专注app软件定制开发七、SpringSecurity OAuth2 + JWT + SpringCloud Gateway实现统一鉴权管理

代码

代码仓库:

代码分支: lesson7

博客:

简介

专注app软件定制开发在上一篇文章中,我们使用SpringSecurity OAuth2 + SpringCloud Gateway专注app软件定制开发搭建了一套符合架构的授权系统,在Gateway专注app软件定制开发网关实现统一身份鉴定、专注app软件定制开发访问权限控制,专注app软件定制开发同时将授权信息下发到下游业务服务中,下游业务服务只需要关注核心业务逻辑。上述架构依赖于auth授权服务器,每一次业务请求都需要使用access_token请求auth授权服务器来获取用户授权信息,如果access_token自带授权信息,那么网关只需要鉴别access_token有效信息,这将会降低系统对auth授权服务器的依赖,JWT(JSON Web Token)将是很好的选择。

JWT

我们这里不详细介绍JWT,有兴趣的同学可以查看阮一峰老师的文章:。JWT定义了一种数据结构,它由三部分组成:

  • Header,头部,定义了签名算法,令牌类型
  • Payload,负载,是一个JSON对象,包含实际应用中使用的数据,例如用户名,用户角色,注意这部分内容是不加密的,因此不能包含保密信息
  • Signature,签名,用于验证JWT是否有效,防止信息内容篡改

JWT内容是不加密的,可以使用解码信息,查看内容。例如有一个JWT格式Token:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UiLCJibG9nIl0sImV4X3VzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCIsInVzZXIiXSwiZXhwIjoxNjU0NTk0MTc2LCJhdXRob3JpdGllcyI6WyJhZG1pbiJdLCJqdGkiOiI2NTdjZmU0Yi05ZDBlLTRhNTUtYjJjOS1iZWE3MTA2YWJkYWIiLCJjbGllbnRfaWQiOiJibG9nIn0.CGQTlvCCwGWIuJBy_qNeX2YBEYYTy6W1FPXOll75P1jdEyvi_TDiTLE4AO2Fa9vtgdWKrtywgGi4kFWZw8mcRFmhVfl9ehdoPcN5Hmdnz-ybJuLWh0i1k0xqg6MsZryTR1wAweEggZkHsIdCZfOw-yPZFTKuhAgVL4d-12Uthb4

在线解析后得到信息如下:

项目改造优化

优化分析

在先前文章中,我们将授权信息保存在OAuth授权服务器中,客户端必须通过请求OAuth授权服务器来获取授权信息,如果使用JWT,并且在JWT中保存相关授权信息,那么可以直接解析JWT获取授权信息(需要验证JWT是有有效)。

在先前的项目中,我们使用SpringSecurity OAuth2默认配置来创建access_token,实际上SpringSecurity OAuth2提供了对JWT格式access_token支持,我们只需要配置access_token的生成方式,需要修改OAuth授权服务中的Token生成方式,以及Gateway网关服务中的Token解析方式进行修改即可

生成JWT格式Token

使用SpringSecurity OAuth2时没有配置TokenService对象,将会默认使用DefaultTokenServices组件来管理access_token, 使用UUID.randomUUID().toString()方式生成access_token和refresh_token,因此token中不包含任何信息。我们需要配置新的TokenService对象来生成JWT格式Token。

SpringSecurity OAuth2提供了以下组件来生成JWT格式Token:

  • JwtTokenStore实现了TokenStore接口,用来管理access_token和refresh_token
  • JwtAccessTokenConverter实现了TokenEnhancer, AccessTokenConverter接口,可以生成JWT格式token

JWT签名算法我们选用安全性更高的非对称加密算法:RSA(在代码auth/src/test/java/com/hzchendou/blog/demo/RSAKeyTest中提供生成RSA Key方法),配置TokenService:

  1. @Bean
  2. public TokenStore tokenStore() {
  3. return new JwtTokenStore(accessTokenConverter());
  4. }
  5. @Bean
  6. public JwtAccessTokenConverter accessTokenConverter() {
  7. JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
  8. converter.setKeyPair(keyPair()); //非对称秘钥,具体参见代码
  9. return converter;
  10. }
  11. @Bean
  12. public AuthorizationServerTokenServices tokenService() {
  13. DefaultTokenServices service = new DefaultTokenServices();
  14. service.setClientDetailsService(authClientDetailService);
  15. service.setSupportRefreshToken(true);
  16. service.setTokenStore(tokenStore);
  17. //令牌增强
  18. TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
  19. List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
  20. tokenEnhancers.add(tokenEnhancer);
  21. tokenEnhancers.add(accessTokenConverter);
  22. tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);
  23. service.setTokenEnhancer(tokenEnhancerChain);
  24. service.setAccessTokenValiditySeconds(60 * 60 * 2); // 令牌默认有效期2小时
  25. service.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3); // 刷新令牌默认有效期3天
  26. return service;
  27. }

将TokenService配置到OAuth服务中:

endpoints.tokenServices(tokenService());//令牌管理服务

解析JWT格式Token

在网关中需要配置JWT格式解析器,因为使用RSA算法,因此在Gateway中需要配置RSA公钥来验证Token有效性,这里与SpringSecurity OAuth2推荐的配置方式有所不同,如果你使用SpringSecurity OAuth2的推荐方式,那么需要配置jwtAuthenticationConverter来解析JWT中的字段:

  1. @Bean
  2. public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
  3. JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
  4. jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
  5. jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
  6. JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
  7. jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
  8. return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
  9. }

下面还需要配置JWT解析验证器来验证JWT的有效性,有多种方式:

SpringSecurity OAuth2提供的方式:

除此之外还需要配置public key信息来验证JWT有效性,在配置文件中配置:

  1. spring:
  2. security:
  3. oauth2:
  4. resourceserver:
  5. jwt:
  6. jwk-set-uri: http://localhost:8081/key/public-key /// 这里需要在oauth授权服务器中配置接口
  7. @RestController
  8. public class KeyController {
  9. @Autowired
  10. KeyPair keyPair;
  11. //获取公钥
  12. @GetMapping("/key/public-key")
  13. public Map<String, Object> getPublicKey() {
  14. RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
  15. RSAKey key = new RSAKey.Builder(publicKey).build();
  16. return new JWKSet(key).toJSONObject();
  17. }
  18. }

直接配置PublicKey方式:

我们这里直接将public key 配置到gateway网关中:

  1. jwt:
  2. rsa:
  3. publickey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCEfoWyfxqYz6j6tczCoELJfCwxpC+iHox7YEvz6slxNworp+CQAC86qt4Rx14lijoufiBMol0/mAABlG1lv3K1LOgQGcwueZDY5nk0uabOWv787moVbQHRTQoAwMIeSDPQ3SgSoEFyHM6Jj/We7XUpAyQEXKk9AabAvywEk2u9ewIDAQAB

然后手动创建JwtDecoder:

  1. @Slf4j
  2. @Configuration
  3. public class TokenConfig {
  4. @Value("${jwt.rsa.publickey}")
  5. private String publicKey;
  6. public RSAPublicKey rsaPublicKey() {
  7. try {
  8. return (RSAPublicKey)RSAUtils.decodePublicKey(publicKey);
  9. } catch (Exception ex) {
  10. log.error("生成 KeyPair 失败", ex);
  11. System.exit(-1);
  12. return null;
  13. }
  14. }
  15. @Bean
  16. public NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder() {
  17. return NimbusReactiveJwtDecoder.withPublicKey(rsaPublicKey())
  18. .signatureAlgorithm(SignatureAlgorithm.from("RS256")).build();
  19. }
  20. }

还有一步需要配置,JWT Token解析后的类型是JwtAuthenticationToken,因此需要修改SecurityGlobalFilter中ReactiveSecurityContextHolder.getContext()方法返回的authentication类型(具体参见代码)

运行校验

分别运行auth、resource、gateway服务:

  • gateway - 8080
  • auth - 8081
  • resource - 8082

发起OAuth密码授权请求POST http://localhost:8080/blog-oauth/oauth/token:请求参数:

  1. client_id:blog
  2. client_secret:blog
  3. grant_type:password
  4. username:admin
  5. password:admin

请求结果:

  1. {
  2. "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UiLCJibG9nIl0sImV4X3VzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCIsInVzZXIiXSwiZXhwIjoxNjU0NTk4MzY3LCJhdXRob3JpdGllcyI6WyJhZG1pbiJdLCJqdGkiOiIyMmVlOTU4My00Y2U5LTRmNzEtOGI4MS02YjViNzdmYWNlYWEiLCJjbGllbnRfaWQiOiJibG9nIn0.atWwzwpCK1ycjf3-EkPUYs4DMqO7rGPIMwMjHKS3FrTKRjMW5DHkQjtilG2EB8qGNBlwQJo0xAnQ_RNMzOjVojGxyb-TUPCubqODnmnYhuee0ho2TurDT5YzfO-Ypkv2SDqEm6Kw38m-oV_93NofGtKNJD1or2kwdoZe6kn4qgw",
  3. "token_type": "bearer",
  4. "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UiLCJibG9nIl0sImV4X3VzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCIsInVzZXIiXSwiYXRpIjoiMjJlZTk1ODMtNGNlOS00ZjcxLThiODEtNmI1Yjc3ZmFjZWFhIiwiZXhwIjoxNjU0ODUwMzY3LCJhdXRob3JpdGllcyI6WyJhZG1pbiJdLCJqdGkiOiJjMzZhNDQ2Mi00ZmE3LTQ2OGUtODNiMS1iYzk4MDFjMzBjMWEiLCJjbGllbnRfaWQiOiJibG9nIn0.IyBeBQMjU-KYGIvvlQTrTkEtrPmTjLZIl1oFvyK0vytOlOFaE4Q5tMOLf1lt1UaBpmi2Tz4ElQSc6EMYX_OKmbyEHSidYxseUr8gE5MVM1raqOPCnR0Dyn7okQ0NvArOB9JuxLTXSa3NoSM3OxRQm2sUS55e6FKpifZ2q7xgGnY",
  5. "expires_in": 7199,
  6. "scope": "all user",
  7. "ex_username": "admin",
  8. "jti": "22ee9583-4ce9-4f71-8b81-6b5b77faceaa"
  9. }

发起资源服务器请求:POST http://localhost:8080/blog-resource/admin/hello,请求头携带token参数:

Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzb3VyY2UiLCJibG9nIl0sImV4X3VzZXJuYW1lIjoiYWRtaW4iLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCIsInVzZXIiXSwiZXhwIjoxNjU0NTk2OTQyLCJhdXRob3JpdGllcyI6WyJhZG1pbiJdLCJqdGkiOiJkOWRhZjhmYS1jOTY4LTQ2YzQtODMyMi1kOTQxNGU3YWZhY2UiLCJjbGllbnRfaWQiOiJibG9nIn0.Jkz_Tlk1W7opXspihyDkp1VFcIXu3ebfVPkshjFcKpktPmqkUIA4D2aWF5A13fq5QGUIDQKf89rVeGHaFfer657J7kqaax2qNT6yuNgmQAu4C8VQkG01VLDsOa-m9xaZnqR_--Af-Z7FbwpZNOT2pBuyP4M3efnMmGhRQQjB4hQ

返回结果:

  1. {
  2. "code": 200,
  3. "data": "Hello Admin"
  4. }

结果符合预期,到此完成JWT + SpringSecurity OAuth2 + SpringCloud Gateway 统一权限访问控制功能

总结

  • JWT自带用户信息,只需要验证Token有效性
  • OAuth授权服务添加JWT TokenService返回JWT格式token
  • Gateway网关服务添加JWTAuthenticationConverter解析JWT信息,同时添加NimbusReactiveJwtDecoder对JWT有效性进行验证

参考文档

联系方式

技术更新换代速度很快,我们无法在有限时间掌握全部知识,但我们可以在他人的基础上进行快速学习,学习也是枯燥无味的,加入我们学习牛人经验:

点击: 

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发