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请求头
AuthorizationBasic 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密码, 存在于用户表中

在这里插入图片描述
在这里插入图片描述

请求流程
请求拦截
BasicAuthenticationFilterBasic认证拦截器
ClientDetailsServiceclient查询接口
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)
AbstractUserDetailsAuthenticationProviderProviderManager175行调用
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;
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值