令牌往哪里存?
客户端信息入库
第三方应用优化
在我们配置授权码模式的时候,有两个东西当时存在了内存中:
我们所使用的 InMemoryTokenStore 实现了 TokenStore 接口,我们来看下 TokenStore 接口的实现类:
可以看到,我们有多种方式来存储 access_token。
首先我们启动一个 Redis 服务,然后给 auth-server 添加 Redis 依赖:
org.springframework.boot
spring-boot-starter-data-redis
依赖添加成功后,在 application.properties 中添加 redis 配置:
spring.redis.host=
spring.redis.port=6379
spring.redis.password=
配置完成后,我们修改 TokenStore 的实例,如下:
package com.xql.authorization_server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
@Configuration
public class AccessTokenConfig {
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean
TokenStore tokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
}
然后分别启动项目,走一遍第三方登录流程,然后我们发现,派发的 access_token 在 redis 中也有一份:
所以我们要将客户端信息存入数据库中。
客户端信息入库涉及到的接口主要是 ClientDetailsService,这个接口主要有两个实现类,如下:
InMemoryClientDetailsService 就不多说了,这是存在内存中的。如果要存入数据库,很明显是 JdbcClientDetailsService,我们来大概看下 JdbcClientDetailsService 的源码,就能分析出数据库的结构了:
CREATE TABLE `oauth_client_details` (
`client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端ID,唯一标识',
`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端访问秘钥,BCryptPasswordEncoder加密算法加密',
`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '可访问资源id(英文逗号分隔)',
`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权范围(英文逗号分隔)',
`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '授权类型(英文逗号分隔)',
`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '重定向uri',
`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '@PreAuthorize("hasAuthority(''admin'')")可以在方法上标志 用户或者说client 需要说明样的权限\r\n\n\n指定客户端所拥有的Spring Security的权限值\r\n(英文逗号分隔)',
`access_token_validity` int NOT NULL COMMENT '令牌有效期(单位:秒)',
`refresh_token_validity` int NOT NULL COMMENT '刷新令牌有效期(单位:秒)',
`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '预留字段,在Oauth的流程中没有实际的使用(JSON格式数据)',
`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '设置用户是否自动Approval操作, 默认值为 ''false''\r\n可选值包括 ''true'',''false'', ''read'',''write''.\r\n该字段只适用于grant_type="authorization_code"的情况,当用户登录成功后,若该值为''true''或支持的scope值,则会跳过用户Approve的页面, 直接授权',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
接下来我们将一开始定义的客户端的关键信息存入数据库中,如下:
xql $2a$10$SMP8P9hRmdTFzMfZBXksuuDNBm7AV9q1SFomvqc9FR38e/MMR7XiC res1 all authorization_code,password, client_credentials, implicit,refresh_token http://localhost:8089/goods/index 7200 259200 false 2023-05-06 01:14:19 2023-05-06 06:14:30
既然用到了数据库,依赖当然也要提供相应的支持,我们给 authorization_server 添加如下依赖:
org.springframework.boot
spring-boot-starter-jdbc
mysql
mysql-connector-java
然后在 application.properties 中配置一下数据库的连接信息:
spring.datasource.river-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://xxxx:3307/oauth?useUnicode=true&characterEncoding=utf8
spring.datasource.username= root
spring.datasource.password= xxxx
这里的配置多了最后一条。这是因为我们一会要创建自己的 ClientDetailsService,而系统已经创建了 ClientDetailsService,加了最后一条就允许我们自己的实例覆盖系统默认的实例。
@Resource
private DataSource dataSource;
@Bean
public ClientDetailsService detailsService(){
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(detailsService());
//内存配置的方式配置用户信息
//内存方式
// clients.inMemory()
// //内存模拟client_id
// .withClient("xql")
// //客户端 秘钥 以及加密方式BCryptPasswordEncoder
// .secret(new BCryptPasswordEncoder().encode("xql123"))
// //客户端拥有的资源列表
// .resourceIds("res1")
// //该client允许的授权类型
// .authorizedGrantTypes("authorization_code",
// "password", "client_credentials", "implicit",
// "refresh_token")
// //允许的授权范围
// .scopes("all")
// //跳转到授权页面
// .autoApprove(false)
// //回调地址
// .redirectUris("http://localhost:8089/goods/index");
// .redirectUris("http://localhost:8089/goods/implicit.jsp");
// .redirectUris("http://localhost:8089/goods/login");
//继续注册其他客户端
// .and()
// .withClient()
// 加载自定义的客户端管理服务
// clients.withClientDetails(clientDetailsService);
}
修改后的 AuthorizationServerTokenServices 实例如下:
@Autowired
private ClientDetailsService clientDetailsService;
/**
* 这个 Bean 主要用来配置 Token 的一些基本信息,
* 例如 Token 是否支持刷新、Token 的存储位置、Token 的有效期以及刷新 Token 的有效期等等。
* Token 有效期这个好理解,刷新 Token 的有效期我说一下,当 Token 快要过期的时候,
* 我们需要获取一个新的 Token,在获取新的 Token 时候,需要有一个凭证信息,
* 这个凭证信息不是旧的 Token,而是另外一个 refresh_token,这个 refresh_token 也是有有效期的。
*/
@Bean
public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
//客户端详情服务
services.setClientDetailsService(clientDetailsService);
//允许令牌自动刷新
services.setSupportRefreshToken(true);
//令牌存储策略-内存
services.setTokenStore(tokenStore);
// 令牌默认有效期2小时
services.setAccessTokenValiditySeconds(60 * 60 * 2);
// 刷新令牌默认有效期3天
services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
return services;
}