使用JWT作为Spring Security OAuth2的token存储
序
Spring Security OAuth2的demo在前几篇文章中已经讲过了,在那些模式中使用的都是RemoteTokenService调用授权服务器来校验token,返回校验通过的用户信息供上下文中获取
这种方式会加重授权服务器的负载,你想啊,当用户没授权时候获取token得找授权服务器,有token了访问资源服务器还要访问授权服务器,相当于说每次请求都要访问授权服务器,这样对授权服务器的负载会很大
常规的方式有两种来解决这个问题:
- 使用JWT作为Token传递
- 使用Redis存储Token,资源服务器本地访问Redis校验Token
使用JWT与Redis都可以在资源服务器中进行校验Token,从而减少授权服务器的工作量
JWT默认使用HMACSHA256对称加密算法,以下记录下默认算法实现与非对称RSA算法的集成,使用不同算法加解密测试方法是一致的,所以放在文章最后
授权服务器整合JWT——对称加解密算法
授权服务器整体代码结构

pom.xml中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
SecurityConfig配置,主要需要显式声明AuthenticationManager和UserDetailsService这两个bean
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
public UserDetailsService userDetailsService(){ //主要是配置这个Bean,用于授权服务器配置中注入
return super.userDetailsService();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter: off
auth.inMemoryAuthentication()
.withUser("hellxz")
.password(passwordEncoder().encode("xyz"))
.authorities(Collections.emptyList());
// @formatter: on
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() //所有请求都需要通过认证
.and()
.httpBasic() //Basic提交
.and()
.csrf().disable(); //关跨域保护
}
}
授权服务器配置AuthorizationConfig
@Configuration
@EnableAuthorizationServer //开启授权服务
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
public UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//允许表单提交
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()")
.tokenKeyAccess("permitAll()");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter: off
clients.inMemory()
.withClient("client-a") //client端唯一标识
.secret(passwordEncoder.encode("client-a-secret")) //client-a的密码,这里的密码应该是加密后的
.authorizedGrantTypes("authorization_code", "password", "refresh_token") //授权模式标识,这里主要测试用password模式,另外refresh_token不是一种模式,但是可以使用它来刷新access_token(在它的有效期内)
.scopes("read_user_info") //作用域
.resourceIds("resource1") //资源id
.redirectUris("http://localhost:9001/callback"); //回调地址
// @formatter: on
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(jwtTokenStore()) //设置jwtToken为tokenStore
.accessTokenConverter(jwtAccessTokenConverter());//设置access_token转换器
}
/**
* jwt访问token转换器
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("my-sign-key"); //资源服务器需要配置此选项方能解密jwt的token
return converter;
}
/**
* jwt的token存储对象
*/
@Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
这里主要是在configure(AuthorizationServerEndpointsConfigurer endpoints)授权服务的端点配置中加入JWT的tokenStore和access_token的转换器,以及这二者的声明Bean方法
这里使用的是默认对称MAC算法,即加密解密使用相同的密钥
启动类就不说了,开启@SpringBootApplicatin的main方法
资源服务器整合JWT——对称加解密算法
资源服务器主要就一个资源配置类
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(HttpSecurity http) throws Exception {
//设置创建session策略
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//@formatter:off
//所有请求必须授权
http.authorizeRequests()
.anyRequest().authenticated();
//@formatter:on
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenStore(jwtTokenStore());
}
/**
* jwt访问token转换器
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("my-sign-key"); //与授权服务器相同的signingKey
return converter;
}
/**
* jwt的token存储对象
*/
@Bean
public JwtTokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
}
配置JWT的TokenStore和AccessTokenConverter与授权服器相同,添加启动类完成配置
OAuth整合JWT——非对称加解密RSA
本部分基于对称加密部分,仅展示需要修改的部分
首先使用keytool生成jks (Java Key Store) 密钥,按提示输入姓氏等信息
keytool -genkeypair -alias hellxz-jwt -validity 3650 -keyalg RSA -keypass hellxzTest -keystore hellxz-jwt.jks -storepass hellxzTest
生成的私钥文件会在当前目录,把hellxz-jwt.jks复制到授权服务器的resources目录下
授权服务器需修改jwtAccessTokenConverter()
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory storeKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource("hellxz-jwt.jks"), "hellxzTest".toCharArray());
converter.setKeyPair(storeKeyFactory.getKeyPair("hellxz-jwt"));
return converter;
}
在hellxz-jwt.jks同目录下,执行命令生成公钥
➜ keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey
输入密钥库口令: hellxzTest
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4
ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI
7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4T
Au76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1
l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+
0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2Uglh
zQIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDUTCCAjmgAwIBAgIEePeDczANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJD
TjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEKMAgGA1UEChMB
MDEKMAgGA1UECxMBMDEOMAwGA1UEAxMFemhhbmcwHhcNMTkxMjE1MDUyOTM2WhcN
MjkxMjEyMDUyOTM2WjBZMQswCQYDVQQGEwJDTjEQMA4GA1UECBMHYmVpamluZzEQ
MA4GA1UEBxMHYmVpamluZzEKMAgGA1UEChMBMDEKMAgGA1UECxMBMDEOMAwGA1UE
AxMFemhhbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFTvO6UVRU
FeZkPbzHAzi6Xl73IWtOguBYoeU0uWn3Tj8ZuJYGhni1wFw2rdXEsYE31U6p8/U/
kLt9GDP3lQjtKEoIqCwUUYvasCqymUwMKU39p1+zGakXTqtUiQaBx721HRDSI41x
o35v+UCsrhMC7vpCxDCf0wteXOdV9zNyVk5lJ8M2O77Um4HOq3p8Q9apqUFRVh1X
TMJQMbyKQ3WX0PSbW1JJoqmJOtTbIRQZSOL7v1SvLtjwEKURfp3gJOX1NACEvmDf
YagkRzRLbL7Rup6pSy/WdS30qIlP2SI68D5DujZPw4e6pBP2V7uS+YiLiBJfm9I2
+9lqATZSCWHNAgMBAAGjITAfMB0GA1UdDgQWBBQF96rK7n0XufnvtJuH9tD9Ixza
6zANBgkqhkiG9w0BAQsFAAOCAQEAuMzWZJhej6+4TGgodQKQ5L5RBtOUbesxA1Ue
s9iA4m/jNZnVCXJE0nY47YVzBCIkIsYALswGooMj1PIJxEMpggXVmIuiJpaPgg+4
sthzISxKzX0ru8IrJTapaglMi74ai6S73LTBSke9GEPgWWnbtdUZoUSiSNt1oJ0J
EhFHdPuzxc36neDFRBOBxW4w3qhsTlKTN2wJm1nLV96nFKmqJhQJhhKt6ihe7hMg
qWxzNsWAqv9gJNdKZt5teqwNKT6H7r1NX5oJkJ0Kn1dZy0O3rDDd5E0KDKkMtwOh
3deJH6Uvtt/dw/drzJlByNDEPp6hYGQu2dW5JG5uiHuzFHnJeA==
-----END CERTIFICATE-----
复制公钥部分到public.cert放到资源服务器的resources目录
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxU7zulFUVBXmZD28xwM4
ul5e9yFrToLgWKHlNLlp904/GbiWBoZ4tcBcNq3VxLGBN9VOqfP1P5C7fRgz95UI
7ShKCKgsFFGL2rAqsplMDClN/adfsxmpF06rVIkGgce9tR0Q0iONcaN+b/lArK4T
Au76QsQwn9MLXlznVfczclZOZSfDNju+1JuBzqt6fEPWqalBUVYdV0zCUDG8ikN1
l9D0m1tSSaKpiTrU2yEUGUji+79Ury7Y8BClEX6d4CTl9TQAhL5g32GoJEc0S2y+
0bqeqUsv1nUt9KiJT9kiOvA+Q7o2T8OHuqQT9le7kvmIi4gSX5vSNvvZagE2Uglh
zQIDAQAB
-----END PUBLIC KEY-----
修改资源服务器jwtAccessTokenConverter()方法
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.cert");
String publicKey;
try {
publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
converter.setVerifierKey(publicKey);
return converter;
}
测试验证
发送POST请求http://localhost:8080/oauth/token?username=hellxz&password=xyz&scope=read_user_info&grant_type=password

返回结果

带token访问资源服务器

测试通过
另外使用JWT应设置尽量短的过期时间,因为JWT的token无法手动revoke,只能等待其到达过期时间失效
使用JWT作为Spring Security OAuth2的token存储的更多相关文章
- 使用Redis作为Spring Security OAuth2的token存储
写在前边 本文对Spring Security OAuth2的token使用Redis保存,相比JWT实现的token存储,Redis可以随时吊销access_token,并且Redis响应速度很快, ...
- Spring Security Oauth2 使用 token 访问资源服务器出现异常:Invalid token does not contain resource id (oauth2)
异常如图 查看资源服务器的日志 p.a.OAuth2AuthenticationProcessingFilter : Authentication request failed: error=&quo ...
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- Spring Security OAuth2.0认证授权六:前后端分离下的登录授权
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战
一. 前言 [APP 移动端]Spring Security OAuth2 手机短信验证码模式 [微信小程序]Spring Security OAuth2 微信授权模式 [管理系统]Spring Se ...
- spring security oauth2 client_credentials模
spring security oauth2 client_credentials模 https://www.jianshu.com/p/1c3eea71410e 序 本文主要简单介绍一下spring ...
- spring security oauth2 搭建认证中心demo
oauth2 介绍 oauth2 协议应该是开发者们耳熟能详的协议了,这里就不做过多的介绍了,具体介绍如何在spring security中搭建oauth2的认证服务.Spring-Securit ...
- spring security oauth2搭建resource-server demo及token改造成JWT令牌
我们在上文讲了如何在spring security的环境中搭建基于oauth2协议的认证中心demo:https://www.cnblogs.com/process-h/p/15688971.html ...
- spring security oauth2 jwt 认证和资源分离的配置文件(java类配置版)
最近再学习spring security oauth2.下载了官方的例子sparklr2和tonr2进行学习.但是例子里包含的东西太多,不知道最简单最主要的配置有哪些.所以决定自己尝试搭建简单版本的例 ...
随机推荐
- C++ const使用总结
这里针对C++中const的一些一般用法进行一下简单的总结 一.定义常量 常量不可修改 : ; 与#define宏定义常量的区别:(1)const常量具有类型,编译器可以进行安全检查:#define宏 ...
- 前端之photoshop的简单使用
常用图片格式 图片是网页制作中很重要的素材,图片有不同的格式,每种格式都有自己的特性,了解这些特效,可以方便我们在制作网页时选取适合的图片格式. 图片格式及特性如下: 1.psd psd是photos ...
- Docker实用debug调试技巧锦集
阅读约 20 分钟 『重用』容器名 但我们在编写/调试Dockerfile的时候我们经常会重复之前的command,比如这种docker run --name jstorm-zookeeper zoo ...
- Python3---AJAX---爬虫
前言 该文章主要介绍面对AJAX的网页如何爬去信息,主要作用是适合刚入门爬虫查看学习 修改时间:20191219 天象独行 首先,我们先介绍一下什么是AJAX,AJAX是与服务器交换数据并跟新部分网页 ...
- webUploader上传大视频文件相关web.config配置
在webuploader上传大文件时必须配置一下,不然请求后台处理程序时,会请求超时.出现404! <system.web> <httpRuntime maxRequestLengt ...
- [b0035] python 归纳 (二十)_多进程数据共享和同步_共享内存Value & Array
1. Code # -*- coding: utf-8 -*- """ 多进程 数据共享 共享变量 Value,Array 逻辑: 2个进程,对同一份数据,一个做加法,一 ...
- oracle通过impdp导入不同表用户、不同表空间的数据
原创 tegwy 发布于2013-04-21 17:34:39 阅读数 46592 收藏 展开 impdp smtj2012/密码@localhost/devdb DIRECTORY=dump_dir ...
- nginx常规扩展功能
功能 语法 配置位置 配置举例 结果验证 备注 文件读取 sendfile on|off ===>(提高读取静态文件效率.直接通过系统内核将文件放入socket,不必再打开一遍) http.se ...
- Python高级特性之:List Comprehensions、Generator、Dictionary and set ...
今天帅气的易哥和大家分享的是Pyton的高级特性,希望大家能和我一起学习这门语言的魅力. Python高级特性之:List Comprehensions.Generator.Dictionary an ...
- RFM模型的应用 - 电商客户细分(转)
RFM模型是网点衡量当前用户价值和客户潜在价值的重要工具和手段.RFM是Rencency(最近一次消费),Frequency(消费频率).Monetary(消费金额) 消费指的是客户在店铺消费最近一次 ...