天天看點

Spring Security OAuth2研究(二) --- OAuth2密碼授權模式Spring Security OAuth2研究(二) — OAuth2密碼授權模式

Spring Security OAuth2研究(二) — OAuth2密碼授權模式

一 、項目搭建

引入依賴

SpringCloud

版本 —

Hoxton.SR3

SpringBoot 2.2.6.RELEASE

<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!--hutool-->
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.3.1</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!--web 子產品-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--undertow容器-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>

    <!--緩存依賴-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
           

SpringBoot

版本

SpringBoot 2.2.6.RELEASE

<properties>
    <java.version>1.8</java.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.security.oauth.boot</groupId>
      <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!--hutool-->
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.3.1</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!--web 子產品-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
      
    <!--undertow容器-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>

    <!--緩存依賴-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-pool2</artifactId>
    </dependency>
  </dependencies>
           

YAML配置檔案

server:
  port: 48888
  tomcat:
    uri-encoding: utf-8
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/markerccc?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&allowPublicKeyRetrieval=true
  application:
    name: auth
  redis:
    database: 0            
    host: localhost
    password:               
    port: 6379              
    timeout: 10000          
    lettuce:
      pool:
        max-active: 8
        max-idle: 8         
        max-wait: 1ms
        min-idle: 0
      shutdown-timeout: 100ms
           

二、書寫代碼

授權伺服器配置

import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * <p>
     *     Description:
     *
     * Parameter 0 of constructor in com.example.demo.config.AuthorizationServerConfig required a single bean, but 2 were found:
     * 	- markClientDetailsServiceImpl: defined in file [E:\IdeaProject\demoAUth\target\classes\com\example\demo\service\MarkClientDetailsServiceImpl.class]
     * 	- clientDetailsService: defined in BeanDefinition defined in class path resource [org/springframework/security/oauth2/config/annotation/configuration/ClientDetailsServiceConfiguration.class]
     * </p>
     */
    /**
     * 總結: 這裡的兩個bean  ClientDetailsService UserDetailsService 請務必與你手撸的名字保持一緻, 這裡注入的名稱請保持與你的類名保持一緻, 不然會出現上面的錯誤
     */
    private final ClientDetailsService markClientDetailsServiceImpl;
    private final AuthenticationManager authenticationManagerBean;
    private final RedisConnectionFactory redisConnectionFactory;
    private final UserDetailsService userDetailsServiceImpl;
    // private final TokenEnhancer pigxTokenEnhancer;

    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.withClientDetails(markClientDetailsServiceImpl);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                .allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .tokenStore(tokenStore())
            	// token增強, 如果需要自己擴充 隻需要注入
                // org.springframework.security.oauth2.provider.token.TokenEnhancer;
                // .tokenEnhancer(tokenEnhancer)   
                .userDetailsService(userDetailsServiceImpl)
                .authenticationManager(authenticationManagerBean)
                .reuseRefreshTokens(false);
    }


    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix("markerccc_abc:");
        tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() {
            @Override
            public String extractKey(OAuth2Authentication authentication) {
                return super.extractKey(authentication) + StrUtil.COLON + "1";
            }
        });
        return tokenStore;
    }
}
           

Web安全配置擴充卡

import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@Primary
@Order(90)
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private MobileSecurityConfigurer mobileSecurityConfigurer;

    @Override
    @SneakyThrows
    protected void configure(HttpSecurity http) {
        http
                .formLogin()
                // .loginPage("/token/login")
                // .loginProcessingUrl("/token/form")
                // .failureHandler(authenticationFailureHandler())
                .and()
                .logout()
                .logoutSuccessHandler((request, response, authentication) -> {
                    String referer = request.getHeader(HttpHeaders.REFERER);
                    response.sendRedirect(referer);
                })
                .deleteCookies("JSESSIONID")
                .invalidateHttpSession(true)
                .and()
                .authorizeRequests()
                .antMatchers(
                        "/token/**",
                        "/actuator/**",
                        "/mobile/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
            	// 這裡是我做手機号登入的配置處理器, 這裡你們可以先去掉
                // .apply(mobileSecurityConfigurer);
    }

    /**
     * 不攔截靜态資源
     *
     * @param web
     */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/css/**");
    }

    @Bean
    @Override
    @SneakyThrows
    public AuthenticationManager authenticationManagerBean() {
        return super.authenticationManagerBean();
    }

    /**
     * https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated Encoded password does not look like
     * BCrypt
     * 這裡的密碼加密模式請參考上面的連結Spring說的很清楚
     *
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}
           

建立Redis配置類

import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnMissingBean(CacheManagerCustomizers.class)
public class RedisCacheManagerConfig {

	@Bean
	public CacheManagerCustomizers cacheManagerCustomizers(
		ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
		return new CacheManagerCustomizers(customizers.getIfAvailable());
	}
}
           
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@EnableCaching
@Configuration
@AllArgsConstructor
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisTemplateConfig {

	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
		redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		return redisTemplate;
	}
}
           

建立User類

import java.util.Collection;
import lombok.Getter;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.User;

public class MarkCCCUser extends User {

   private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

   /**
    * 使用者ID
    */
   @Getter
   private Integer id;
   /**
    * 部門ID
    */
   @Getter
   private Integer deptId;

   /**
    * 手機号
    */
   @Getter
   private String phone;

   /**
    * 頭像
    */
   @Getter
   private String avatar;


   /**
    * Construct the <code>User</code> with the details required by
    * {@link DaoAuthenticationProvider}.
    *
    * @param id                    使用者ID
    * @param deptId                部門ID
    * @param tenantId              租戶ID
    * @param username              the username presented to the
    *                              <code>DaoAuthenticationProvider</code>
    * @param password              the password that should be presented to the
    *                              <code>DaoAuthenticationProvider</code>
    * @param enabled               set to <code>true</code> if the user is enabled
    * @param accountNonExpired     set to <code>true</code> if the account has not expired
    * @param credentialsNonExpired set to <code>true</code> if the credentials have not
    *                              expired
    * @param accountNonLocked      set to <code>true</code> if the account is not locked
    * @param authorities           the authorities that should be granted to the caller if they
    *                              presented the correct username and password and the user is enabled. Not null.
    * @throws IllegalArgumentException if a <code>null</code> value was passed either as
    *                                  a parameter or as an element in the <code>GrantedAuthority</code> collection
    */
   public MarkCCCUser(Integer id, Integer deptId, String phone, String avatar, String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
      super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
      this.id = id;
      this.deptId = deptId;
      this.phone = phone;
      this.avatar = avatar;
   }
}
           

建立UserDetailsServiceImpl類

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class MarkUserDetailsServiceImpl implements MarkUserDetailsService {

    // private final RemoteUserService remoteUserService;
    private final CacheManager cacheManager;

    /**
     * 使用者密碼登入
     *
     * @param username 使用者名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    @SneakyThrows
    public UserDetails loadUserByUsername(String username) {
     
        // 查詢使用者具體實作, 自己去實作
        Set<String> dbAuthsSet = new HashSet<>();
        Collection<? extends GrantedAuthority> authorities
                = AuthorityUtils.createAuthorityList(dbAuthsSet.toArray(new String[0]));

        return new MarkCCCUser(1, 1, "1xxxxxxxxxx", "1", 1, "markerccc", "{noop}123456", true, true, true, true, authorities);
    }
}
           

建立ClientDetailsService的實作類

@Slf4j
@Service
public class MarkClientDetailsServiceImpl extends JdbcClientDetailsService {

    public MarkClientDetailsServiceImpl(DataSource dataSource) {
        super(dataSource);
    }


    /**
     * 重寫原生方法支援redis緩存
     *
     * @param clientId
     * @return ClientDetails
     * @throws InvalidClientException
     */
    @Override
    @Cacheable(value = "mark_oauth:client:details", key = "#clientId", unless = "#result == null")
    public ClientDetails loadClientByClientId(String clientId) {
  super.setSelectClientDetailsSql(String.format("請自己引用下面的SQL", "1"));
        return super.loadClientByClientId(clientId);
    }
}
           
SELECT client_id,
       CONCAT('{noop}', client_secret) AS client_secret,
       resource_ids,
       scope,
       authorized_grant_types,
       web_server_redirect_uri,
       authorities,
       access_token_validity,
       refresh_token_validity,
       additional_information,
       autoapprove
FROM sys_oauth_client_details
WHERE client_id = ?
  AND del_flag = 0
  AND tenant_id = %s
           

建立Application類

@SpringCloudApplication
// @SpringBootApplication  使用SpringBoot時請用這個注解
public class OAuth2Application {

    public static void main(String[] args) {
        SpringApplication.run(OAuth2Application.class, args);
    }

}
           

三、開始測試

請求路徑

使用

postman

請求路徑

localhost:48888/oauth/token

參數

header 請求頭

Authorization

Basic client_id:client_secret

client_id:client_secret

這裡需要變成

Base64

加密
這個資料存于

sys_oauth_client_details

ClientDetailsService

類查詢而出, 具體實作為

MarkClientDetailsServiceImpl

form-data 表單

grant_type

授權模式, 存在于

sys_oauth_client_details

authorized_grant_types

字段中

username

使用者名, 存在于使用者表中

password

密碼, 存在于使用者表中
x-www-form-urlencoded 表單

grant_type

授權模式, 存在于

sys_oauth_client_details

authorized_grant_types

字段中

username

使用者名, 存在于使用者表中

password

密碼, 存在于使用者表中
Spring Security OAuth2研究(二) --- OAuth2密碼授權模式Spring Security OAuth2研究(二) — OAuth2密碼授權模式
Spring Security OAuth2研究(二) --- OAuth2密碼授權模式Spring Security OAuth2研究(二) — OAuth2密碼授權模式
請求流程
請求攔截

BasicAuthenticationFilter

Basic認證攔截器

ClientDetailsService

client

查詢接口

InMemoryClientDetailsService

從記憶體中查詢

client

, 實作

ClientDetailsService

JdbcClientDetailsService

從資料庫中查詢

client

, 實作

ClientDetailsService

MarkClientDetailsServiceImpl

實作

JdbcClientDetailsService

請求開始

AbstractEndpoint

實作

InitializingBean

AuthorizationEndpoint

繼承

AbstractEndpoint

這裡不是重點

TokenEndpoint

繼承

AbstractEndpoint

這個為密碼授權模式的入口

TokenEndpoint.postAccessToken()

該方法上有

@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)

這個注解

ClientDetailsService.loadClientByClientId()

查詢

sys_oauth_client_details

資訊, 位于

postAccessToken()

的96行

getTokenGranter().grant()

進行授權, 位于

postAccessToken()

的132行, ``TokenGranter

拿到的是

TokenGranter`

TokenGranter

授權接口

AbstractTokenGranter

實作

TokenGranter

getAccessToken(client, tokenRequest)

ResourceOwnerPasswordTokenGranter

繼承

AbstractTokenGranter

getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest)

getAccessToken(client, tokenRequest)

調用

authenticationManager.authenticate(userAuth)

AuthenticationManager

認證管理器

ProviderManager

provider.authenticate(authentication)

AbstractUserDetailsAuthenticationProvider

ProviderManager

175行調用

retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication)

AbstractUserDetailsAuthenticationProvider

144行調用

DaoAuthenticationProvider

this.getUserDetailsService().loadUserByUsername(username)

查詢使用者資訊

MarkUserDetailsServiceImpl

調用由我們重寫的方法查詢使用者

四、表結構

/*
 Navicat Premium Data Transfer

 Source Server         : 127.0.0.1
 Source Server Type    : MySQL
 Source Server Version : 50729
 Source Host           : localhost:3306
 Source Schema         : markerccc

 Target Server Type    : MySQL
 Target Server Version : 50729
 File Encoding         : 65001

 Date: 28/04/2020 14:57:54
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `sys_oauth_client_details`;
CREATE TABLE `sys_oauth_client_details`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `client_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
  `tenant_id` int(11) NOT NULL DEFAULT 0 COMMENT '所屬租戶',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '終端資訊表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_oauth_client_details
-- ----------------------------
INSERT INTO `sys_oauth_client_details` VALUES (1, 'app', NULL, 'app', 'server', 'password,refresh_token,authorization_code,client_credentials,implicit', NULL, NULL, 43200, 2592001, NULL, 'true', '0', 1);
INSERT INTO `sys_oauth_client_details` VALUES (2, 'daemon', NULL, 'daemon', 'server', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, 'true', '0', 1);
INSERT INTO `sys_oauth_client_details` VALUES (3, 'gen', NULL, 'gen', 'server', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, 'true', '0', 1);
INSERT INTO `sys_oauth_client_details` VALUES (4, 'mp', NULL, 'mp', 'server', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, 'true', '0', 1);
INSERT INTO `sys_oauth_client_details` VALUES (5, 'test', NULL, 'test', 'server', 'password,refresh_token,authorization_code,client_credentials', NULL, NULL, NULL, NULL, NULL, 'false', '0', 1);

SET FOREIGN_KEY_CHECKS = 1;