在Spring Security Oauth2-授权码模式(Finchley版本)一文中介绍了OAuth2的授权码模式的实现,本文将在这篇文章的基础上使用JWT生成token。关于JWT的介绍可以参考JWT详解。

一、准备工作

  1. 下载代码
    大家可以在github上下载Spring Security Oauth2-授权码模式(Finchley版本)的源码。
  2. 添加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相关推荐

  1. 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【密码认证流程】 总结

    在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...

  2. 搭建认证服务器 - Spring Security Oauth2.0 集成 Jwt 之 【授权码认证流程】 总结

    在搭建介绍流程之前,确保您已经搭建了一个 Eureka 注册中心,因为没有注册中心的话会报错(也有可能我搭建的认证服务器是我项目的一个子模块的原因):Request execution error. ...

  3. Spring Security OAuth2 实现使用JWT

    https://yq.aliyun.com/articles/263390 1.Maven 配置 首先,我们需要在我们的pom.xml中添加spring-security-jwt依赖项. <de ...

  4. Spring Security OAuth2:整合jwt

    接着上一篇博客:https://www.cnblogs.com/wwjj4811/p/14505081.html 1|0JWT介绍 JSON Web Token(JWT)是一个开放的行业标准(RFC ...

  5. Spring Security OAuth2整合JWT

    文章目录 1. Spring Security 与 OAuth2 2. Spring Security OAuth2的配置和使用 ①:引入依赖 ②:配置 spring security ③:配置授权服 ...

  6. Spring Security OAuth2 单点登录和登出

    文章目录 1. 单点登录 1.1 使用内存保存客户端和用户信息 1.1.1 认证中心 auth-server 1.1.2 子系统 service-1 1.1.3 测试 1.2 使用数据库保存客户端和用 ...

  7. java oauth sso 源码_基于Spring Security Oauth2的SSO单点登录+JWT权限控制实践

    概 述 在前文<基于Spring Security和 JWT的权限系统设计>之中已经讨论过基于 Spring Security和 JWT的权限系统用法和实践,本文则进一步实践一下基于 Sp ...

  8. 基于 Spring Security OAuth2和 JWT 构建保护微服务系统

    我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的.尤其是前后端完全分离之后,我们的用户信息不一 ...

  9. 《深入理解 Spring Cloud 与微服务构建》第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统

    <深入理解 Spring Cloud 与微服务构建>第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统 文章目录 <深入理解 Spring Cl ...

最新文章

  1. java file_Java IO: File
  2. Golang正则笔记 :使用正则表达式处理题库文本
  3. LinearAlgebra_3
  4. java linux 起多个进程_linux下tomcat启动后出现多个java进程
  5. java之servlet
  6. LDAP落地实战(三):GitLab集成OpenLDAP认证
  7. cocos2d-x学习笔记 动作 CCCallFunc家族(回调函数包装器)
  8. 一滴油怎样造就了一种健康生活方式?
  9. 安科 OJ 1190 连接电脑 (并查集)
  10. poj 2914(stoer_wanger算法求全局最小割)
  11. close关闭指定窗口 matlab_Δ-Σ ADC设计笔记一:MATLAB环境设置
  12. js简单验证码的生成和验证
  13. matlab画线不同颜色_怎样画线框图才有意义?
  14. CSS快速学习7:定位锚点
  15. A trip through the Graphics Pipeline 2011_06_(Triangle) rasterization and setup
  16. 协同过滤推荐算法的用户向量相似度计算
  17. java python混合编程_python+java混合编程
  18. [谈现在的PSP与NDSi]
  19. 《代码整洁之道》第14章 逐步改进 的代码片段
  20. 深度学习:马尔可夫随机场

热门文章

  1. 测试工程师必备测试常识
  2. React Hooks核心原理与实战
  3. Qbao Network 1024周年庆, 邀请Q 宝宝和金主爸爸来抱锦鲤啦!
  4. logstash收集日志写入redis
  5. 掌握农业信息化核心 物联网助力智慧农业
  6. MySqlException(0x80004005) 报错
  7. R语言数据分析、展现与实例(06)
  8. 【Mac 教程系列第 9 篇】如何把 Mac 中的程序坞移动到另一个屏幕
  9. 学习torchvision.models._dict_
  10. java实现.费诺编码_信息论编码实验报告费诺编码附源代码