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, "15072146145", "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_detailsClientDetailsService類查詢而出, 具體實現爲MarkClientDetailsServiceImpl
form-data 表單
grant_type 授權模式, 存在於sys_oauth_client_detailsauthorized_grant_types字段中
username 用戶名, 存在於用戶表中
password 密碼, 存在於用戶表中
x-www-form-urlencoded 表單
grant_type 授權模式, 存在於sys_oauth_client_detailsauthorized_grant_types字段中
username 用戶名, 存在於用戶表中
password 密碼, 存在於用戶表中

在這裏插入圖片描述
在這裏插入圖片描述

請求流程
請求攔截
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 ProviderManager175行調用
retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication) AbstractUserDetailsAuthenticationProvider144行調用
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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章