基于 JDBC 存储令牌
#概述
本章节 基于 JDBC 存储令牌 的模式用于演示最基本的操作,帮助大家快速理解 oAuth2 认证服务器中 "认证"、"授权"、"访问令牌” 的基本概念
操作流程
初始化 oAuth2 相关表
在数据库中配置客户端
配置认证服务器
配置数据源:
DataSource
配置令牌存储方式:
TokenStore
->JdbcTokenStore
配置客户端读取方式:
ClientDetailsService
->JdbcClientDetailsService
配置服务端点信息:
AuthorizationServerEndpointsConfigurer
tokenStore
:设置令牌存储方式
配置客户端信息:
ClientDetailsServiceConfigurer
withClientDetails
:设置客户端配置读取方式
配置 Web 安全
- 配置密码加密方式:
BCryptPasswordEncoder
- 配置认证信息:
AuthenticationManagerBuilder
- 配置密码加密方式:
通过 GET 请求访问认证服务器获取授权码
- 端点:
/oauth/authorize
- 端点:
通过 POST 请求利用授权码访问认证服务器获取令牌
- 端点:
/oauth/token
- 端点:
附:默认的端点 URL
/oauth/authorize
:授权端点/oauth/token
:令牌端点/oauth/confirm_access
:用户确认授权提交端点/oauth/error
:授权服务错误信息端点/oauth/check_token
:用于资源服务访问的令牌解析端点/oauth/token_key
:提供公有密匙的端点,如果你使用 JWT 令牌的话
#初始化 oAuth2 相关表
使用官方提供的建表脚本初始化 oAuth2 相关表,地址如下:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
由于我们使用的是 MySQL 数据库,默认建表语句中主键为 VARCHAR(256)
,这超过了最大的主键长度,请手动修改为 128
,并用 BLOB
替换语句中的 LONGVARBINARY
类型,修改后的建表脚本如下:
CREATE TABLE `clientdetails` (
`appId` varchar(128) NOT NULL,
`resourceIds` varchar(256) DEFAULT NULL,
`appSecret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`grantTypes` varchar(256) DEFAULT NULL,
`redirectUrl` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additionalInformation` varchar(4096) DEFAULT NULL,
`autoApproveScopes` varchar(256) DEFAULT NULL,
PRIMARY KEY (`appId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(128) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
`authentication` blob,
`refresh_token` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) DEFAULT NULL,
`clientId` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`status` varchar(10) DEFAULT NULL,
`expiresAt` timestamp NULL DEFAULT NULL,
`lastModifiedAt` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(128) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_client_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication_id` varchar(128) NOT NULL,
`user_name` varchar(256) DEFAULT NULL,
`client_id` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_code` (
`code` varchar(256) DEFAULT NULL,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL,
`token` blob,
`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
#在数据库中配置客户端
在表 oauth_client_details
中增加一条客户端配置记录,需要设置的字段如下:
client_id
:客户端标识client_secret
:客户端安全码,此处不能是明文,需要加密scope
:客户端授权范围authorized_grant_types
:客户端授权类型web_server_redirect_uri
:服务器回调地址
使用 BCryptPasswordEncoder
为客户端安全码加密,代码如下:
System.out.println(new BCryptPasswordEncoder().encode("secret"));
1
数据库配置客户端效果图如下:
#POM
由于使用了 JDBC 存储,我们需要增加相关依赖,数据库连接池部分弃用 Druid
改为 HikariCP
(号称全球最快连接池)
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>${hikaricp.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
#配置认证服务器
创建一个类继承 AuthorizationServerConfigurerAdapter
并添加相关注解:
@Configuration
@EnableAuthorizationServer
package ml.yompc.spring.security.oauth2.server.configure;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* 授权
* @email yom535@outlook.com
* @author: 有民(yom535)
* @date: 2019/8/21
* @time: 15:52
*/
@Configuration
@EnableAuthorizationServer
public class AuthrizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
//替换数据源
@Bean
@Primary //有两个DataSource配置设置这个是主要的
@ConfigurationProperties(prefix = "spring.datasource") //走配置文件的配置
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
public TokenStore tokenStore(){
return new JdbcTokenStore(dataSource());
}
public ClientDetailsService jdbcclientDetailsService(){
return new JdbcClientDetailsService(dataSource());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
//配置客户端
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcclientDetailsService());
}
}
#服务器安全配置
创建一个类继承 WebSecurityConfigurerAdapter
并添加相关注解:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
:全局方法拦截
package ml.yompc.spring.security.oauth2.server.configure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 认证
* @email yom535@outlook.com
* @author: 有民(yom535)
* @date: 2019/8/21
* @time: 15:41
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
//加密
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//内存模式
auth.inMemoryAuthentication()
//设置用户名和密码并且必须加密。也必须设置角色,角色规范要大写
.withUser("user").password(passwordEncoder().encode("123456")).roles("USER")
.and()//加用户
.withUser("admin").password(passwordEncoder().encode("admin888")).roles("ADMIN");
}
}
application.yml
注意此处的jdbc-url
,原本是url
,但是Spring oUath2 要求是jdbc-url
spring:
application:
name: oAuth2-service
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://ip:port/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 12345678
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
server:
port: 8080
#访问获取授权码
打开浏览器,输入地址:
http://localhost:8080/oauth/authorize?client_id=client&response_type=code
第一次访问会跳转到登录页面
验证成功后会询问用户是否授权客户端
选择授权后会跳转到我的博客,浏览器地址上还会包含一个授权码(code=1JuO6V
),浏览器地址栏会显示如下地址:
http://www.yompc.ml/?code=1JuO6V
有了这个授权码就可以获取访问令牌了
#通过授权码向服务器申请令牌
通过 CURL 或是 Postman 请求
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'grant_type=authorization_code&code=1JuO6V' "http://client:secret@localhost:8080/oauth/token"
1
得到响应结果如下:
{
"access_token": "016d8d4a-dd6e-4493-b590-5f072923c413",
"token_type": "bearer",
"expires_in": 43199,
"scope": "app"
}
操作成功后数据库 oauth_access_token
表中会增加一笔记录,效果图如下: