Spring Security OAuth2实现使用JWT
在Spring Security Oauth2-授权码模式(Finchley版本)一文中介绍了OAuth2的授权码模式的实现,本文将在这篇文章的基础上使用JWT生成token。关于JWT的介绍可以参考JWT详解。
一、准备工作
- 下载代码
大家可以在github上下载Spring Security Oauth2-授权码模式(Finchley版本)的源码。 - 添加JWT依赖
授权服务和资源服务是两个分开的服务,需要在两个服务中添加JWT依赖<dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.7.RELEASE</version> </dependency>
二、案例介绍
JWT认证提供了对称加密和非对称加密的实现。
2.1 对称加密
2.1.1 授权服务
(1) 定义token的生成方式
AccessToken转换器用来定义token的生成方式,这里使用JWT生成token
@Beanpublic TokenStore tokenStore(){return new JwtTokenStore(accessTokenConverter());}@Beanpublic JwtAccessTokenConverter accessTokenConverter() {final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("123");return converter;}
(2) 告知spring security token的生成方式
/*** 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)* @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//指定认证管理器endpoints.authenticationManager(authenticationManager);//指定token存储位置endpoints.tokenStore(tokenStore());// token生成方式endpoints.accessTokenConverter(accessTokenConverter());endpoints.userDetailsService(userDetailsService);}
2.1.2 资源服务
资源服务的配置与授权服务大致相同
/*** 资源服务器配置** @author simon* @create 2018-11-14 11:03**/
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {@Beanpublic JwtAccessTokenConverter accessTokenConverter(){JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setSigningKey("123");return converter;}@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(accessTokenConverter());}@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {DefaultTokenServices defaultTokenServices = new DefaultTokenServices();defaultTokenServices.setTokenStore(tokenStore());resources.tokenServices(defaultTokenServices;}
}
2.2 非对称加密
使用非对称密钥(公钥和私钥)来执行签名过程,需要先生成一个证书并导出公钥。
2.2.1 生成证书
(1) 生成JKS Java KeyStore文件
使用命令行工具keytool生成证书
keytool -genkeypair -alias mytest -keyalg RSA -keypass mypass -keystore mytest.jks -storepass mypass
此命令将生成一个名为mytest.jks的文件,其中包含我们的密钥(公钥和私钥)。
(2) 导出公钥
我们可以使用下面的命令从生成的JKS中导出我们的公钥:
keytool -list -rfc --keystore mytest.jks | openssl x509 -inform pem -pubkey
结果如下:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp
OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2
/5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3
DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR
xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr
lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK
eQIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIEGtZIUzANBgkqhkiG9w0BAQsFADA2MQswCQYDVQQGEwJ1
czELMAkGA1UECBMCY2ExCzAJBgNVBAcTAmxhMQ0wCwYDVQQDEwR0ZXN0MB4XDTE2
MDMxNTA4MTAzMFoXDTE2MDYxMzA4MTAzMFowNjELMAkGA1UEBhMCdXMxCzAJBgNV
BAgTAmNhMQswCQYDVQQHEwJsYTENMAsGA1UEAxMEdGVzdDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAICCtlreMdhLQ5eNQu736TrDKrmTMjsrXjtkbFXj
Cxf4VyHmL4nCq9EkM1ZKHRxAQjIhl0A8+aa4o06t0Rz8tv+ViQQmKu8h4Ey77KTM
urIr1zezXWBOyOaV6Pyh5OJ8/hWuj9y/Pi/dBP96sH+o9wylpwICRUWPAG0mF7dX
eRC4iBtf4BKswtH2ZjYYX6wbccFl65aVA09Cn739EFZj0ccQi10/rRHtbHlhhKnj
iy+b10S6ps2XAXtUWfZEEJuN/mvUJ+YnEkZw30wHrENwq5QFiSpdpHFlNR8CasPn
WUUmdV+JBFzTMsz3TwWxplOjB3YacsCO0imU+5l+AQ51CnkCAwEAAaMhMB8wHQYD
VR0OBBYEFOGefUBGquEX9Ujak34PyRskHk+WMA0GCSqGSIb3DQEBCwUAA4IBAQB3
1eLfNeq45yO1cXNl0C1IQLknP2WXg89AHEbKkUOA1ZKTOizNYJIHW5MYJU/zScu0
yBobhTDe5hDTsATMa9sN5CPOaLJwzpWV/ZC6WyhAWTfljzZC6d2rL3QYrSIRxmsp
/J1Vq9WkesQdShnEGy7GgRgJn4A8CKecHSzqyzXulQ7Zah6GoEUD+vjb+BheP4aN
hiYY1OuXD+HsdKeQqS+7eM5U7WW6dz2Q8mtFJ5qAxjY75T0pPrHwZMlJUhUZ+Q2V
FfweJEaoNB9w9McPe1cAiE+oeejZ0jq0el3/dJsx3rlVqZN+lMhRJJeVHFyeb3XF
lLFCUGhA7hxn2xf3x1JW
-----END CERTIFICATE-----
这里我们只需要复制公钥到资源服务的resources目录下的public.txt 文件中
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIK2Wt4x2EtDl41C7vfp
OsMquZMyOyteO2RsVeMLF/hXIeYvicKr0SQzVkodHEBCMiGXQDz5prijTq3RHPy2
/5WJBCYq7yHgTLvspMy6sivXN7NdYE7I5pXo/KHk4nz+Fa6P3L8+L90E/3qwf6j3
DKWnAgJFRY8AbSYXt1d5ELiIG1/gEqzC0fZmNhhfrBtxwWXrlpUDT0Kfvf0QVmPR
xxCLXT+tEe1seWGEqeOLL5vXRLqmzZcBe1RZ9kQQm43+a9Qn5icSRnDfTAesQ3Cr
lAWJKl2kcWU1HwJqw+dZRSZ1X4kEXNMyzPdPBbGmU6MHdhpywI7SKZT7mX4BDnUK
eQIDAQAB
-----END PUBLIC KEY-----
2.2.2 授权服务
将刚刚生成的证书复制到授权服务器的resources目录下。配置JwtAccessTokenConverter使用mytest.jks 中的KeyPair
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();KeyStoreKeyFactory keyStoreKeyFactory =new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));return converter;}
2.2.3 资源服务
配置资源服务器使用公钥:
@Bean
public JwtAccessTokenConverter accessTokenConverter(){JwtAccessTokenConverter converter = new JwtAccessTokenConverter();Resource resource = new ClassPathResource("public.txt");String publicKey;try {publicKey = inputStream2String(resource.getInputStream());} catch (final IOException e) {throw new RuntimeException(e);}converter.setVerifierKey(publicKey);return converter;
}String inputStream2String(InputStream is) throws IOException {BufferedReader in = new BufferedReader(new InputStreamReader(is));StringBuffer buffer = new StringBuffer();String line = "";while ((line = in.readLine()) != null) {buffer.append(line);}return buffer.toString();
}
2.3 添加额外信息
额外信息的添加与加密方式无关
2.3.1 自定义生成token携带的信息
可以自定义一个TokenEnhancer将额外的信息添加到token中。TokenEnhancer 接口提供public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication)
方法用于token信息的添加
(1) 自定义TokenEnhancer
/*** 自定义token生成携带的信息** @author simon* @create 2018-11-14 10:16**/
public class CustomTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {final Map<String, Object> additionalInfo = new HashMap<>();//获取登录信息UserDetails user = (UserDetails) oAuth2Authentication.getUserAuthentication().getPrincipal();additionalInfo.put("userName", user.getUsername());additionalInfo.put("authorities", user.getAuthorities());((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(additionalInfo);return oAuth2AccessToken;}
}
(2) 将自定义的TokenEnhancer加入到TokenEnhancerChain中
/*** 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)* @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//指定认证管理器endpoints.authenticationManager(authenticationManager);//指定token存储位置endpoints.tokenStore(tokenStore());endpoints.accessTokenConverter(accessTokenConverter());endpoints.userDetailsService(userDetailsService);//自定义token生成方式TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customerEnhancer(),accessTokenConverter()));endpoints.tokenEnhancer(tokenEnhancerChain);
2.3.2 自定义token中添加的信息
(1)授权服务自定义JwtAccessTokenConverte
JwtAccessTokenConverter是我们用来生成token的转换器,所以我们需要配置这里面的部分信息来实现token中携带额外的信息。
JwtAccessTokenConverter默认使用DefaultAccessTokenConverter来处理token的生成、转换、获取。DefaultAccessTokenConverter中使用UserAuthenticationConverter来处理token与userinfo的获取、转换。因此我们需要重写下UserAuthenticationConverter对应的转换方法就可以
/*** 自定义CustomerAccessTokenConverter 这个类的作用主要用于AccessToken的转换,* 默认使用DefaultAccessTokenConverter 这个装换器* DefaultAccessTokenConverter有个UserAuthenticationConverter,这个转换器作用是把用户的信息放入token中,默认只是放入user_name* <p>* 自定义这个方法,加入了额外的信息* <p>* @author simon* @create 2018-11-14 10:26**/
public class CustomerAccessTokenConverter extends DefaultAccessTokenConverter {public CustomerAccessTokenConverter() {super.setUserTokenConverter(new CustomerUserAuthenticationConverter());}private class CustomerUserAuthenticationConverter extends DefaultUserAuthenticationConverter{@Overridepublic Map<String, ?> convertUserAuthentication(Authentication authentication) {LinkedHashMap <String, Object> response = new LinkedHashMap <>();response.put("details", authentication.getDetails());response.put("test","hello");if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {response.put("authorities", AuthorityUtils.authorityListToSet(authentication.getAuthorities()));}return response;}}
}
(2) 授权服务告诉JwtAccessTokenConverter替换默认的方式
@Bean
public JwtAccessTokenConverter accessTokenConverter() {final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();KeyStoreKeyFactory keyStoreKeyFactory =new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));converter.setAccessTokenConverter(new CustomerAccessTokenConverter());return converter;
}
(3)资源服务自定义JwtAccessTokenConverte
/*** 自定义CustomerAccessTokenConverter 这个类的作用主要用于AccessToken的转换,* 默认使用DefaultAccessTokenConverter 这个装换器* DefaultAccessTokenConverter有个UserAuthenticationConverter,这个转换器作用是把用户的信息放入token中,* 默认只是放入username* <p>* 自定义了下这个方法,加入了额外的信息* <p>* @author simon* @create 2018-11-14 10:26**/
public class CustomerAccessTokenConverter extends DefaultAccessTokenConverter {public CustomerAccessTokenConverter() {super.setUserTokenConverter(new CustomerUserAuthenticationConverter());}private class CustomerUserAuthenticationConverter extends DefaultUserAuthenticationConverter {// 资源服务获得自定义信息 @Overridepublic Authentication extractAuthentication(Map<String, ?> map) {Collection <? extends GrantedAuthority> authorities = this.getAuthorities(map);return new UsernamePasswordAuthenticationToken(map, "N/A", authorities);}private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {if (!map.containsKey("authorities")) {return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(new String[]{"USER"}));} else {Object authorities = map.get("authorities");if (authorities instanceof String) {return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);} else if (authorities instanceof Collection) {return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils.collectionToCommaDelimitedString((Collection) authorities));} else {throw new IllegalArgumentException("Authorities must be either a String or a Collection");}}}}
}
2.4 测试
启动服务
2.4.1 获取code
浏览器访问http://localhost:8080/oauth/authorize?response_type=code&client_id=client1&redirect_uri=http://baidu.com
进入登录页面,输入用户名:admin;密码:admin。
登录成功进入授权页面,点击授权,获得code
https://www.baidu.com/?code=NW8eB1
2.4.2 获取token
使用POSTMAN发送post请求获取token
2.4.3 访问资源服务获取资源
使用POSTMAN发送get请求获取资源
2.4.4 解析token
新增测试类解析token
@Testpublic void contextLoads() {//填写tokenString token = "";Jwt jwt = JwtHelper.decode(token);System.err.println(jwt.toString());}
解析后的信息如下:
{"alg":"RS256","typ":"JWT"} {"test":"hello","scope":["test"],"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":"34B189EA6F1DA4834E5AEA31E91A2460"},"exp":1542277115,"userName":"admin","authorities":[{"authority":"USER"}],"jti":"8e4a72d3-affb-4977-b174-cb9ee4f2e08b","client_id":"client1"} [256 crypto bytes]
结果中包含添加的额外信息
github源码下载
Spring Security OAuth2实现使用JWT相关推荐
- 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【密码认证流程】 总结
在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...
- 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结
在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...
- Spring Security OAuth2 实现使用JWT
https://yq.aliyun.com/articles/263390 1.Maven 配置 首先,我们需要在我们的pom.xml中添加spring-security-jwt依赖项. <de ...
- Spring Security OAuth2:整合jwt
接着上一篇博客:https://www.cnblogs.com/wwjj4811/p/14505081.html 1|0JWT介绍 JSON Web Token(JWT)是一个开放的行业标准(RFC ...
- Spring Security OAuth2整合JWT
文章目录 1. Spring Security 与 OAuth2 2. Spring Security OAuth2的配置和使用 ①:引入依赖 ②:配置 spring security ③:配置授权服 ...
- Spring Security OAuth2 单点登录和登出
文章目录 1. 单点登录 1.1 使用内存保存客户端和用户信息 1.1.1 认证中心 auth-server 1.1.2 子系统 service-1 1.1.3 测试 1.2 使用数据库保存客户端和用 ...
- java oauth sso 源码_基于Spring Security Oauth2的SSO单点登录+JWT权限控制实践
概 述 在前文<基于Spring Security和 JWT的权限系统设计>之中已经讨论过基于 Spring Security和 JWT的权限系统用法和实践,本文则进一步实践一下基于 Sp ...
- 基于 Spring Security OAuth2和 JWT 构建保护微服务系统
我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的.尤其是前后端完全分离之后,我们的用户信息不一 ...
- 《深入理解 Spring Cloud 与微服务构建》第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统
<深入理解 Spring Cloud 与微服务构建>第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统 文章目录 <深入理解 Spring Cl ...
最新文章
- java file_Java IO: File
- Golang正则笔记 :使用正则表达式处理题库文本
- LinearAlgebra_3
- java linux 起多个进程_linux下tomcat启动后出现多个java进程
- java之servlet
- LDAP落地实战(三):GitLab集成OpenLDAP认证
- cocos2d-x学习笔记 动作 CCCallFunc家族(回调函数包装器)
- 一滴油怎样造就了一种健康生活方式?
- 安科 OJ 1190 连接电脑 (并查集)
- poj 2914(stoer_wanger算法求全局最小割)
- close关闭指定窗口 matlab_Δ-Σ ADC设计笔记一:MATLAB环境设置
- js简单验证码的生成和验证
- matlab画线不同颜色_怎样画线框图才有意义?
- CSS快速学习7:定位锚点
- A trip through the Graphics Pipeline 2011_06_(Triangle) rasterization and setup
- 协同过滤推荐算法的用户向量相似度计算
- java python混合编程_python+java混合编程
- [谈现在的PSP与NDSi]
- 《代码整洁之道》第14章 逐步改进 的代码片段
- 深度学习:马尔可夫随机场
热门文章
- 测试工程师必备测试常识
- React Hooks核心原理与实战
- Qbao Network 1024周年庆, 邀请Q 宝宝和金主爸爸来抱锦鲤啦!
- logstash收集日志写入redis
- 掌握农业信息化核心 物联网助力智慧农业
- MySqlException(0x80004005) 报错
- R语言数据分析、展现与实例(06)
- 【Mac 教程系列第 9 篇】如何把 Mac 中的程序坞移动到另一个屏幕
- 学习torchvision.models._dict_
- java实现.费诺编码_信息论编码实验报告费诺编码附源代码