“
摘要: 原创出处 http://www.iocoder.cn/Spring-Security/OAuth2-learning-store/ 「芋道源码」欢迎转载,保留摘要,谢谢!1. 概述
2. 数据库存储器
3. Redis 存储器
4. JWT 存储器
666. 彩蛋“
本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-68-spring-security-oauth 目录。
原创不易,给点个 Star 嘿,一起冲鸭!
1. 概述
在《芋道 Spring Security OAuth2 入门》文章中,我们完成了 Spring Security OAuth 框架的学习。但是我们在文末中也提到,采用基于内存的 InMemoryTokenStore,实现访问令牌和刷新令牌的存储。它会存在两个明显的缺点:重启授权服务器时,令牌信息会丢失,导致用户需要重新授权。
多个授权服务器时,令牌信息无法共享,导致用户一会授权成功,一会授权失败。
因此,本文我们来学习 Spring Security OAuth 提供的其它存储器。TokenStore 是 Spring Security OAuth 定义的令牌存储器接口,它有如下实现类:
TokenStore 类图基于数据库的 JdbcTokenStore
基于 Redis 的 RedisTokenStore
基于 JWT 的 JwtTokenStore
下面,我们逐个小节来演示每个 TokenStore 的配置与使用。
2. 数据库存储器“
示例代码对应仓库:授权服务器:lab-68-demo11-authorization-server-by-jdbc-store
本小节,我们使用基于数据库存储的 JdbcTokenStore。
复制出 lab-68-demo11-authorization-server-by-jdbc-store 项目,进行改造接入 JdbcTokenStore 存储器。最终项目如下图所示:
项目结构
2.1 初始化数据库
① 执行 schema.sql 脚本,创建数据库表结构。drop table if exists oauth_client_details;
create table oauth_client_details (
client_id VARCHAR(255) PRIMARY KEY,
resource_ids VARCHAR(255),
client_secret VARCHAR(255),
scope VARCHAR(255),
authorized_grant_types VARCHAR(255),
web_server_redirect_uri VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(255)
);
create table if not exists oauth_client_token (
token_id VARCHAR(255),
token LONG VARBINARY,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255)
);
create table if not exists oauth_access_token (
token_id VARCHAR(255),
token LONG VARBINARY,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255),
authentication LONG VARBINARY,
refresh_token VARCHAR(255)
);
create table if not exists oauth_refresh_token (
token_id VARCHAR(255),
token LONG VARBINARY,
authentication LONG VARBINARY
);
create table if not exists oauth_code (
code VARCHAR(255), authentication LONG VARBINARY
);
create table if not exists oauth_approvals (
userId VARCHAR(255),
clientId VARCHAR(255),
scope VARCHAR(255),
status VARCHAR(10),
expiresAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
lastModifiedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
结果如下图所示:
表结构表作用oauth_access_tokenOAuth 2.0 访问令牌
oauth_refresh_tokenOAuth 2.0 刷新令牌
oauth_codeOAuth 2.0 授权码
oauth_client_detailsOAuth 2.0 客户端
oauth_client_token
oauth_approvals“
旁白君:这里的表结构设计,我们可以借鉴参考,实现自己的 OAuth 2.0 的功能。
② 执行 data.sql 脚本,插入一个客户端记录。INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
web_server_redirect_uri, authorities, access_token_validity,
refresh_token_validity, additional_information, autoapprove)
VALUES
('clientapp', '112233', 'read_userinfo,read_contacts',
'password,refresh_token', null, null, 3600, 864000, null, true);
结果如下图所示:
oauth_client_details 表记录
2.2 引入依赖
修改 pom.xml 文件,额外引入数据库连接池的依赖:
org.springframework.boot
spring-boot-starter-jdbc
mysql
mysql-connector-java
5.1.48
2.3 配置文件
创建 application.yaml 配置文件,添加数据库连接池的配置:spring:
# datasource 数据源配置内容,对应 DataSourceProperties 配置属性类
datasource:
url: jdbc:mysql://127.0.0.1:43063/demo-68-authorization-server?useSSL=false&useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.jdbc.Driver
username: root # 数据库账号
password: 123456 # 数据库密码
2.4 OAuth2AuthorizationServerConfig
修改 OAuth2AuthorizationServerConfig 配置类,设置使用 JdbcTokenStore 和 ClientDetailsService。代码如下:@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
/**
* 用户认证 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 数据源 DataSource
*/
@Autowired
private DataSource dataSource;
@Bean
public TokenStore jdbcTokenStore(){
return new JdbcTokenStore(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
endpoints.authenticationManager(authenticationManager)
.tokenStore(jdbcTokenStore());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception{
oauthServer.checkTokenAccess("isAuthenticated()");
}
@Bean
public ClientDetailsService jdbcClientDetailsService(){
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
clients.withClientDetails(jdbcClientDetailsService());
}
}
具体的修改点,如下图所示:
修改点
2.5 简单测试
执行 AuthorizationServerApplication 启动授权服务器。下面,我们使用Postman 模拟一个 Client。
① POST 请求 http://localhost:8080/oauth/token 地址,使用密码模式进行授权。如下图所示:
密码模式的认证
② 查询 oauth_access_token 和 oauth_refresh_token 表,查看访问令牌和刷新令牌。如下图所示:
oauth_access_tokenoauth_refresh_token
3. Redis 存储器“
示例代码对应仓库:授权服务器:lab-68-demo11-authorization-server-by-redis-store
本小节,我们使用基于 Redis 存储的 RedisTokenStore。
复制出 lab-68-demo11-authorization-server-by-redis-store 项目,进行改造接入 RedisTokenStore 存储器。最终项目如下图所示:
项目结构
3.1 引入依赖
修改 pom.xml 文件,额外引入 Spring Data Redis 的依赖:
org.springframework.boot
spring-boot-starter-data-redis
“
友情提示:想要学习 Spring Data Redis 的胖友,可以看看《芋道 Spring Boot Redis 入门》文章。
3.2 配置文件
创建 application.yaml 配置文件,添加 Spring Data Redis 的配置:spring:
# 对应 RedisProperties 类
redis:
host: 127.0.0.1
port: 6379
3.3 OAuth2AuthorizationServerConfig
修改 OAuth2AuthorizationServerConfig 配置类,设置使用 RedisTokenStore。代码如下:@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
/**
* 用户认证 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* Redis 连接的工厂
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
endpoints.authenticationManager(authenticationManager)
.tokenStore(redisTokenStore());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception{
oauthServer.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
clients.inMemory()
.withClient("clientapp").secret("112233") // Client 账号、密码。
.authorizedGrantTypes("password", "refresh_token") // 密码模式
.scopes("read_userinfo", "read_contacts") // 可授权的 Scope
// .and().withClient() // 可以继续配置新的 Client
;
}
}
具体的修改点,如下图所示:
修改点
3.4 简单测试
执行 AuthorizationServerApplication 启动授权服务器。下面,我们使用Postman 模拟一个 Client。
① POST 请求 http://localhost:8080/oauth/token 地址,使用密码模式进行授权。如下图所示:
密码模式的认证
② 查看 Redis 中的 访问令牌和刷新令牌。如下图所示:
RDM 查看 Redis
4. JWT 存储器“
示例代码对应仓库:授权服务器:lab-68-demo11-authorization-server-by-jwt-store
本小节,我们使用基于 JWT 存储的 JwtTokenStore。“
友情提示:如果胖友对 JWT 不了解的胖友,可以先看看如下两篇文章:《JSON Web Token - 在Web应用间安全地传递信息》
《八幅漫画理解使用 JSON Web Token 设计单点登录系统》
复制出 lab-68-demo11-authorization-server-by-jwt-store 项目,进行改造接入 JwtTokenStore 存储器。最终项目如下图所示:
项目结构
4.1 OAuth2AuthorizationServerConfig
修改 OAuth2AuthorizationServerConfig 配置类,设置使用 JwtTokenStore。代码如下:@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter{
/**
* 用户认证 Manager
*/
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("nainai_zui_shuai"); // JWT 秘钥
return converter;
}
@Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
endpoints.authenticationManager(authenticationManager)
.tokenStore(jwtTokenStore())
.accessTokenConverter(jwtAccessTokenConverter());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception{
oauthServer.checkTokenAccess("isAuthenticated()");
// oauthServer.tokenKeyAccess("isAuthenticated()")
// .checkTokenAccess("isAuthenticated()");
// oauthServer.tokenKeyAccess("permitAll()")
// .checkTokenAccess("permitAll()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
clients.inMemory()
.withClient("clientapp").secret("112233") // Client 账号、密码。
.authorizedGrantTypes("password", "refresh_token") // 密码模式
.scopes("read_userinfo", "read_contacts") // 可授权的 Scope
// .and().withClient() // 可以继续配置新的 Client
;
}
}
具体的修改点,如下图所示:
修改点
4.2 简单测试
执行 AuthorizationServerApplication 启动授权服务器。下面,我们使用Postman 模拟一个 Client。
① POST 请求 http://localhost:8080/oauth/token 地址,使用密码模式进行授权。如下图所示:
密码模式的认证
② 使用 https://jwt.io/ 提供的工具,解析 JWT 令牌。如下图所示:
JWT 解析
666. 彩蛋
比较简单的一篇文章,下一篇《芋道 Spring Security OAuth2 单点登录》走起!