《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Spring-Security/OAuth2-learning-store/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labslab-68-spring-security-oauth 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

《芋道 Spring Security OAuth2 入门》文章中,我们完成了 Spring Security OAuth 框架的学习。但是我们在文末中也提到,采用基于内存InMemoryTokenStore,实现访问令牌和刷新令牌的存储。它会存在两个明显的缺点

  • 重启授权服务器时,令牌信息会丢失,导致用户需要重新授权。
  • 多个授权服务器时,令牌信息无法共享,导致用户一会授权成功,一会授权失败。

因此,本文我们来学习 Spring Security OAuth 提供的其它存储器。TokenStore 是 Spring Security OAuth 定义的令牌存储器接口,它有如下实现类:

TokenStore 类图

下面,我们逐个小节来演示每个 TokenStore 的配置与使用。

2. 数据库存储器

示例代码对应仓库:

本小节,我们使用基于数据库存储的 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_token OAuth 2.0 访问令牌
oauth_refresh_token OAuth 2.0 刷新令牌
oauth_code OAuth 2.0 授权码
oauth_client_details OAuth 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 文件,额外引入数据库连接池的依赖:

<!-- 实现对数据库连接池的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency> <!-- 本示例,我们使用 MySQL -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>

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 配置类,设置使用 JdbcTokenStoreClientDetailsService。代码如下:

@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_tokenoauth_refresh_token 表,查看访问令牌和刷新令牌。如下图所示:

`oauth_access_token`

`oauth_refresh_token`

3. Redis 存储器

示例代码对应仓库:

本小节,我们使用基于 Redis 存储的 RedisTokenStore

复制lab-68-demo11-authorization-server-by-redis-store 项目,进行改造接入 RedisTokenStore 存储器。最终项目如下图所示:

项目结构

3.1 引入依赖

修改 pom.xml 文件,额外引入 Spring Data Redis 的依赖:

<!-- 实现对 Spring Data Redis 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

友情提示:想要学习 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 存储器

示例代码对应仓库:

本小节,我们使用基于 JWT 存储的 JwtTokenStore

友情提示:如果胖友对 JWT 不了解的胖友,可以先看看如下两篇文章:

复制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 单点登录》走起!

文章目录
  1. 1. 1. 概述
  2. 2. 2. 数据库存储器
    1. 2.1. 2.1 初始化数据库
    2. 2.2. 2.2 引入依赖
    3. 2.3. 2.3 配置文件
    4. 2.4. 2.4 OAuth2AuthorizationServerConfig
    5. 2.5. 2.5 简单测试
  3. 3. 3. Redis 存储器
    1. 3.1. 3.1 引入依赖
    2. 3.2. 3.2 配置文件
    3. 3.3. 3.3 OAuth2AuthorizationServerConfig
    4. 3.4. 3.4 简单测试
  4. 4. 4. JWT 存储器
    1. 4.1. 4.1 OAuth2AuthorizationServerConfig
    2. 4.2. 4.2 简单测试
  5. 5. 666. 彩蛋