天天看点

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;