• Spring Cloud Oauth2 扩展


    1、基于Oauth2的 JWT 令牌信息扩展

            OAuth2帮我们生成的JWT令牌载荷部分信息有限,关于用户信息只有一个user_name,有些场景下我们希望放入一些扩展信息项,比如,之前我们经常向session中存入userId,或者现在我希望在JWT的载荷部分存入当时请求令牌的客户端IP,客户端携带令牌访问资源服务时,可以对比当前请求的客户端真实IP和令牌中存放的客户端IP是否匹配,不匹配拒绝请求,以此进一步提高安全性。那么如何在OAuth2环境下向JWT令牌中存如扩展信息?

    (1)认证服务器生成JWT令牌时存入扩展信息(比如clientIp)

    继承DefaultAccessTokenConverter类重写convertAccessToken方法存入扩展信息

    1. package com.lagou.edu.config;
    2. import org.springframework.security.oauth2.common.OAuth2AccessToken;
    3. import org.springframework.security.oauth2.provider.OAuth2Authentication;
    4. import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
    5. import org.springframework.stereotype.Component;
    6. import org.springframework.web.context.request.RequestContextHolder;
    7. import org.springframework.web.context.request.ServletRequestAttributes;
    8. import javax.servlet.http.HttpServletRequest;
    9. import java.util.Map;
    10. @Component
    11. public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {
    12. @Override
    13. public Map convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
    14. // 获取到request对象
    15. HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
    16. // 获取客户端ip(注意:如果是经过代理之后到达当前服务的话,那么这种方式获取的并不是真实的浏览器客户端ip)
    17. String remoteAddr = request.getRemoteAddr();
    18. Map stringMap = (Map) super.convertAccessToken(token, authentication);
    19. stringMap.put("clientIp",remoteAddr);
    20. return stringMap;
    21. }
    22. }

    (2)将自定义的转换器对象注入

    1. package com.lagou.edu.config;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.context.annotation.Bean;
    4. import org.springframework.context.annotation.Configuration;
    5. import org.springframework.http.HttpMethod;
    6. import org.springframework.security.authentication.AuthenticationManager;
    7. import org.springframework.security.jwt.crypto.sign.MacSigner;
    8. import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
    9. import org.springframework.security.jwt.crypto.sign.Signer;
    10. import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
    11. import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
    12. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
    13. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
    14. import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
    15. import org.springframework.security.oauth2.provider.ClientDetailsService;
    16. import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
    17. import org.springframework.security.oauth2.provider.token.*;
    18. import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
    19. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    20. import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    21. import javax.sql.DataSource;
    22. import java.util.ArrayList;
    23. import java.util.List;
    24. /**
    25. * 当前类为Oauth2 server的配置类(需要继承特定的父类 AuthorizationServerConfigurerAdapter)
    26. */
    27. @Configuration
    28. @EnableAuthorizationServer // 开启认证服务器功能
    29. public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
    30. @Autowired
    31. private AuthenticationManager authenticationManager;
    32. @Autowired
    33. private LagouAccessTokenConvertor lagouAccessTokenConvertor;
    34. private String sign_key = "lagou123"; // jwt签名密钥
    35. /**
    36. * 认证服务器最终是以api接口的方式对外提供服务(校验合法性并生成令牌、校验令牌等)
    37. * 那么,以api接口方式对外的话,就涉及到接口的访问权限,我们需要在这里进行必要的配置
    38. * @param security
    39. * @throws Exception
    40. */
    41. @Override
    42. public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    43. super.configure(security);
    44. // 相当于打开endpoints 访问接口的开关,这样的话后期我们能够访问该接口
    45. security
    46. // 允许客户端表单认证
    47. .allowFormAuthenticationForClients()
    48. // 开启端口/oauth/token_key的访问权限(允许)
    49. .tokenKeyAccess("permitAll()")
    50. // 开启端口/oauth/check_token的访问权限(允许)
    51. .checkTokenAccess("permitAll()");
    52. }
    53. /**
    54. * 客户端详情配置,
    55. * 比如client_id,secret
    56. * 当前这个服务就如同QQ平台,拉勾网作为客户端需要qq平台进行登录授权认证等,提前需要到QQ平台注册,QQ平台会给拉勾网
    57. * 颁发client_id等必要参数,表明客户端是谁
    58. * @param clients
    59. * @throws Exception
    60. */
    61. @Override
    62. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    63. super.configure(clients);
    64. // 从内存中加载客户端详情
    65. /*clients.inMemory()// 客户端信息存储在什么地方,可以在内存中,可以在数据库里
    66. .withClient("client_lagou") // 添加一个client配置,指定其client_id
    67. .secret("abcxyz") // 指定客户端的密码/安全码
    68. .resourceIds("autodeliver") // 指定客户端所能访问资源id清单,此处的资源id是需要在具体的资源服务器上也配置一样
    69. // 认证类型/令牌颁发模式,可以配置多个在这里,但是不一定都用,具体使用哪种方式颁发token,需要客户端调用的时候传递参数指定
    70. .authorizedGrantTypes("password","refresh_token")
    71. // 客户端的权限范围,此处配置为all全部即可
    72. .scopes("all");*/
    73. // 从数据库中加载客户端详情
    74. clients.withClientDetails(createJdbcClientDetailsService());
    75. }
    76. @Autowired
    77. private DataSource dataSource;
    78. @Bean
    79. public JdbcClientDetailsService createJdbcClientDetailsService() {
    80. JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
    81. return jdbcClientDetailsService;
    82. }
    83. /**
    84. * 认证服务器是玩转token的,那么这里配置token令牌管理相关(token此时就是一个字符串,当下的token需要在服务器端存储,
    85. * 那么存储在哪里呢?都是在这里配置)
    86. * @param endpoints
    87. * @throws Exception
    88. */
    89. @Override
    90. public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    91. super.configure(endpoints);
    92. endpoints
    93. .tokenStore(tokenStore()) // 指定token的存储方法
    94. .tokenServices(authorizationServerTokenServices()) // token服务的一个描述,可以认为是token生成细节的描述,比如有效时间多少等
    95. .authenticationManager(authenticationManager) // 指定认证管理器,随后注入一个到当前类使用即可
    96. .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
    97. }
    98. /*
    99. 该方法用于创建tokenStore对象(令牌存储对象)
    100. token以什么形式存储
    101. */
    102. public TokenStore tokenStore(){
    103. //return new InMemoryTokenStore();
    104. // 使用jwt令牌
    105. return new JwtTokenStore(jwtAccessTokenConverter());
    106. }
    107. /**
    108. * 返回jwt令牌转换器(帮助我们生成jwt令牌的)
    109. * 在这里,我们可以把签名密钥传递进去给转换器对象
    110. * @return
    111. */
    112. public JwtAccessTokenConverter jwtAccessTokenConverter() {
    113. JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    114. jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥
    115. jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致
    116. jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
    117. return jwtAccessTokenConverter;
    118. }
    119. /**
    120. * 该方法用户获取一个token服务对象(该对象描述了token有效期等信息)
    121. */
    122. public AuthorizationServerTokenServices authorizationServerTokenServices() {
    123. // 使用默认实现
    124. DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    125. defaultTokenServices.setSupportRefreshToken(true); // 是否开启令牌刷新
    126. defaultTokenServices.setTokenStore(tokenStore());
    127. // 针对jwt令牌的添加
    128. defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
    129. // 设置令牌有效时间(一般设置为2个小时)
    130. defaultTokenServices.setAccessTokenValiditySeconds(20); // access_token就是我们请求资源需要携带的令牌
    131. // 设置刷新令牌的有效时间
    132. defaultTokenServices.setRefreshTokenValiditySeconds(259200); // 3天
    133. return defaultTokenServices;
    134. }
    135. }

    2、资源服务器取出 JWT 令牌扩展信息

            资源服务器也需要自定义一个转换器类,继承DefaultAccessTokenConverter重写extractAuthentication提取方法,把载荷信息设置到认证对象的details属性中。

    1. package com.lagou.edu.config;
    2. import org.springframework.security.oauth2.common.OAuth2AccessToken;
    3. import org.springframework.security.oauth2.provider.OAuth2Authentication;
    4. import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
    5. import org.springframework.stereotype.Component;
    6. import org.springframework.web.context.request.RequestContextHolder;
    7. import org.springframework.web.context.request.ServletRequestAttributes;
    8. import javax.servlet.http.HttpServletRequest;
    9. import java.util.Map;
    10. @Component
    11. public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {
    12. @Override
    13. public OAuth2Authentication extractAuthentication(Map map) {
    14. OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
    15. oAuth2Authentication.setDetails(map); // 将map放入认证对象中,认证对象在controller中可以拿到
    16. return oAuth2Authentication;
    17. }
    18. }

    将自定义的转换器对象注入

    1. package com.lagou.edu.config;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    5. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    6. import org.springframework.security.config.http.SessionCreationPolicy;
    7. import org.springframework.security.jwt.crypto.sign.MacSigner;
    8. import org.springframework.security.jwt.crypto.sign.RsaVerifier;
    9. import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
    10. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    11. import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    12. import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
    13. import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
    14. import org.springframework.security.oauth2.provider.token.TokenStore;
    15. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    16. import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    17. @Configuration
    18. @EnableResourceServer // 开启资源服务器功能
    19. @EnableWebSecurity // 开启web访问安全
    20. public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
    21. private String sign_key = "lagou123"; // jwt签名密钥
    22. @Autowired
    23. private LagouAccessTokenConvertor lagouAccessTokenConvertor;
    24. /**
    25. * 该方法用于定义资源服务器向远程认证服务器发起请求,进行token校验等事宜
    26. * @param resources
    27. * @throws Exception
    28. */
    29. @Override
    30. public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    31. /*// 设置当前资源服务的资源id
    32. resources.resourceId("autodeliver");
    33. // 定义token服务对象(token校验就应该靠token服务对象)
    34. RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
    35. // 校验端点/接口设置
    36. remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
    37. // 携带客户端id和客户端安全码
    38. remoteTokenServices.setClientId("client_lagou");
    39. remoteTokenServices.setClientSecret("abcxyz");
    40. // 别忘了这一步
    41. resources.tokenServices(remoteTokenServices);*/
    42. // jwt令牌改造
    43. resources.resourceId("autodeliver").tokenStore(tokenStore()).stateless(true);// 无状态设置
    44. }
    45. /**
    46. * 场景:一个服务中可能有很多资源(API接口)
    47. * 某一些API接口,需要先认证,才能访问
    48. * 某一些API接口,压根就不需要认证,本来就是对外开放的接口
    49. * 我们就需要对不同特点的接口区分对待(在当前configure方法中完成),设置是否需要经过认证
    50. *
    51. * @param http
    52. * @throws Exception
    53. */
    54. @Override
    55. public void configure(HttpSecurity http) throws Exception {
    56. http // 设置session的创建策略(根据需要创建即可)
    57. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
    58. .and()
    59. .authorizeRequests()
    60. .antMatchers("/autodeliver/**").authenticated() // autodeliver为前缀的请求需要认证
    61. .antMatchers("/demo/**").authenticated() // demo为前缀的请求需要认证
    62. .anyRequest().permitAll(); // 其他请求不认证
    63. }
    64. /*
    65. 该方法用于创建tokenStore对象(令牌存储对象)
    66. token以什么形式存储
    67. */
    68. public TokenStore tokenStore(){
    69. //return new InMemoryTokenStore();
    70. // 使用jwt令牌
    71. return new JwtTokenStore(jwtAccessTokenConverter());
    72. }
    73. /**
    74. * 返回jwt令牌转换器(帮助我们生成jwt令牌的)
    75. * 在这里,我们可以把签名密钥传递进去给转换器对象
    76. * @return
    77. */
    78. public JwtAccessTokenConverter jwtAccessTokenConverter() {
    79. JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    80. jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥
    81. jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致
    82. jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor); //p
    83. return jwtAccessTokenConverter;
    84. }
    85. }

            业务类比如Controller类中,可以通过SecurityContextHolder.getContext().getAuthentication()获取到认证对象,进一步获取到扩展信息 

    Object details =SecurityContextHolder.getContext().getAuthentication().getDetails();

            获取到扩展信息后,就可以做其他的处理了,比如根据userId进一步处理,或者根据clientIp处理,或者其他都是可以的了。

    3、其他

      关于JWT令牌我们需要注意

    • JWT令牌就是一种可以被验证的数据组织格式,它的玩法很灵活,我们这里是基于Spring Cloud Oauth2 创建、校验JWT令牌
    • 我们也可以自己写工具类生成、校验JWT令牌
    • JWT令牌中不要存放过于敏感的信息,因为我们知道拿到令牌后,我们可以解码看到载荷部分的信息
    • JWT令牌每次请求都会携带,内容过多,会增加网络带宽占用
  • 相关阅读:
    LINUX之文件
    uni-app---- 点击按钮拨打电话功能&&点击按钮调用高德地图进行导航的功能【安卓app端】
    【FPGA】FPGA入门 —— 基本开发流程
    网站被DDOS攻击怎么办?防护经验!
    非类型模板参数+模板的特化
    一种车载监控数据存储方法
    nginx的正向代理和反向代理以及负载均衡
    Ajax 相关问题
    mongodb聚合查询示例
    通过openssl库计算字符串或文件sha256
  • 原文地址:https://blog.csdn.net/weixin_52851967/article/details/126642566