• SpringCloud-OAuth2(四):改造篇


    本片主要讲SpringCloud Oauth2篇的实战改造,如动态权限、集成JWT、更改默认url、数据库加载client信息等改造。
    同时,这应该也是我这系列博客的完结篇。
    
    关于Oauth2,我也想说几句:
    如果真的要应用到企业级项目当中去,必须要进行充足的准备,因为默认的配置、UI等很多都不是通用的(不适用于各个公司),
    但是这套框架还好留了很多适配方法等,因此其需要修改的配置、处理器、方法重写等逻辑确实很多很多。
    
    话不多说,正文开始。
    
    

    1:动态权限

    常用的权限校验机制如以下几点:

    参考:SpringCloud+SpringSecurity+Oauth2实现角色权限四种方案 - 知乎

    类型示例
    硬编码如在接口上添加注解:@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    HttpSecurity动态增加在资源服务配置类中配置,如:.authorizeRequests().anyRequest().authenticated()

    如果出现用户所拥有的权限出现变化时,上述两种是无法满足的。


    百度了几天后网上确实有同学给出了不错的示例,其工作机制如下:

    1.1:AccessDecisionManager重写

    1. @Component
    2. public class VipAccessDecisionManager implements AccessDecisionManager {
    3. /**
    4. * 决定当前用户是否有权限访问该请求
    5. **/
    6. @Override
    7. public void decide(Authentication authentication, Object object,
    8. Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
    9. for (ConfigAttribute configAttribute : configAttributes) {
    10. //将访问所需资源或用户拥有资源进行比对
    11. String needAuthority = configAttribute.getAttribute();
    12. if (needAuthority == null) {
    13. continue;
    14. }
    15. for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
    16. if (needAuthority.trim().equals(grantedAuthority.getAuthority())) {
    17. return;
    18. }
    19. }
    20. }
    21. throw new AccessDeniedException("抱歉,您没有访问权限");
    22. }
    23. @Override
    24. public boolean supports(ConfigAttribute configAttribute) {
    25. return true;
    26. }
    27. @Override
    28. public boolean supports(Class aClass) {
    29. return true;
    30. }
    31. }

    1.2:FilterInvocationSecurityMetadataSource重写

    1. public class VipSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    2. private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
    3. private Set<PermRoleEntity> permRoleEntitySet;
    4. private final FilterInvocationSecurityMetadataSource superMetadataSource;
    5. private final VipSecurityOauthService vipSecurityOauthService;
    6. public VipSecurityMetadataSource(FilterInvocationSecurityMetadataSource superMetadataSource,
    7. VipSecurityOauthService vipSecurityOauthService) {
    8. this.superMetadataSource = superMetadataSource;
    9. this.vipSecurityOauthService = vipSecurityOauthService;
    10. }
    11. private void loadPerms() {
    12. permRoleEntitySet = vipSecurityOauthService.loadPerms();
    13. }
    14. /**
    15. * 返回能访问该请求的所有角色集合
    16. **/
    17. @Override
    18. public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    19. loadPerms();
    20. FilterInvocation fi = (FilterInvocation) object;
    21. String access_uri = fi.getRequestUrl();
    22. for (PermRoleEntity permRoleEntity : permRoleEntitySet) {
    23. if (ANT_PATH_MATCHER.match(permRoleEntity.getAccessUri(), access_uri))
    24. return permRoleEntity.getConfigAttributeList();
    25. }
    26. return superMetadataSource.getAttributes(object);
    27. }
    28. @Override
    29. public Collection<ConfigAttribute> getAllConfigAttributes() {
    30. loadPerms();
    31. Set<ConfigAttribute> attributeSet = new HashSet<>();
    32. permRoleEntitySet.stream().map(PermRoleEntity::getConfigAttributeList).forEach(attributeSet::addAll);
    33. return attributeSet;
    34. }
    35. @Override
    36. public boolean supports(Class clazz) {
    37. return FilterInvocation.class.isAssignableFrom(clazz);
    38. }
    39. }

    1.3:动态权限入口(自定义)

    VipSecurityOauthService:权限的动态加载api
    这里写成静态的,好参考。

    1. @Component
    2. public class VipSecurityOauthService {
    3. /**
    4. * 动态加载权限-角色信息
    5. **/
    6. public Set loadPerms() {
    7. Set permRoleEntitySet = new HashSet<>();
    8. permRoleEntitySet.add(new PermRoleEntity().setAccessUri("/demo/admin").setConfigAttributeList(SecurityConfig.createList("admin")));
    9. permRoleEntitySet.add(new PermRoleEntity().setAccessUri("/auth/**").setConfigAttributeList(SecurityConfig.createList("admin")));
    10. permRoleEntitySet.add(new PermRoleEntity().setAccessUri("/demo/sp-admin").setConfigAttributeList(SecurityConfig.createList("sp_admin")));
    11. return permRoleEntitySet;
    12. }
    13. }

    PermRoleEntity:url和角色对应关系

    1. @Data
    2. @Accessors(chain = true)
    3. public class PermRoleEntity {
    4. /**
    5. * 访问的接口
    6. **/
    7. private String accessUri;
    8. /**
    9. * 可访问该接口的角色集合
    10. **/
    11. private List<ConfigAttribute> configAttributeList;
    12. }

    1.4:ResourceServer进行配置

    1.5:测试

    获取用户角色为admin的token,进行接口访问


    ①:用户admin角色的用户访问admin管理的接口


    ①:用户admin角色的用户访问sp-admin管理的接口

    2:集成JWT

    2.1:需要配置的bean

    1. @Bean
    2. public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
    3. return new JwtTokenStore(jwtAccessTokenConverter);
    4. }
    5. @Bean
    6. @Primary
    7. public AuthorizationServerTokenServices defaultTokenServices(TokenStore tokenStore,
    8. JwtAccessTokenConverter jwtAccessTokenConverter,
    9. ClientDetailsService clientDetailsService,
    10. AuthenticationManager authenticationManager) {
    11. DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    12. defaultTokenServices.setSupportRefreshToken(true);
    13. defaultTokenServices.setTokenStore(tokenStore);
    14. defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter);
    15. defaultTokenServices.setAccessTokenValiditySeconds(3600);
    16. defaultTokenServices.setRefreshTokenValiditySeconds(7200);
    17. defaultTokenServices.setClientDetailsService(clientDetailsService);
    18. defaultTokenServices.setAuthenticationManager(authenticationManager);
    19. return defaultTokenServices;
    20. }
    21. @Bean
    22. public JwtAccessTokenConverter jwtAccessTokenConverter() {
    23. JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    24. // 签名密钥
    25. jwtAccessTokenConverter.setSigningKey("sign_key");
    26. // 验证密钥
    27. jwtAccessTokenConverter.setVerifier(new MacSigner("sign_key"));
    28. return jwtAccessTokenConverter;
    29. }

    2.2:认证服务配置更改


    tokenservice: 创建token、刷新token的地方。


    获取token

    2.3:OAuth2 Client

    在前文中提到 OAuth2 Client Service 处理请求的时候是无法识别token的,需要远程提交给认证中心(OAuth2 Server)去识别token。
    token换成JWT后客户端服务(OAuth2 Client Service)也可以识别token了,均需做如下配置:

    1. @Bean
    2. public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
    3. return new JwtTokenStore(jwtAccessTokenConverter);
    4. }
    5. @Bean
    6. public JwtAccessTokenConverter jwtAccessTokenConverter() {
    7. JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
    8. // 签名密钥
    9. jwtAccessTokenConverter.setSigningKey("sign_key");
    10. // 验证密钥
    11. jwtAccessTokenConverter.setVerifier(new MacSigner("sign_key"));
    12. return jwtAccessTokenConverter;
    13. }

    前文中提到的这项配置也可以丢弃了。


    访问客户端服务的接口测试:

    3:更改默认url

    3.1:框架提供的接口

    默认url作用
    /oauth/authorize授权端点
    /oauth/token令牌端点
    /oauth/confirm_access用户批准授权的端点
    /oauth/error用于渲染授权服务器的错误
    /oauth/check_token资源服务器解码access token
    /oauth/check_token当使用JWT的时候,暴露公钥的端点

    3.2:如何更改

    可以按照下面这张方法进行重新转交接口地址:

    自定义处理接口代码:

    1. @RestController
    2. public class AuthController extends WebApiController {
    3. private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
    4. private final ConsumerTokenServices consumerTokenServices;
    5. private final TokenEndpoint tokenEndpoint;
    6. private final AuthorizationEndpoint authorizationRequest;
    7. private final WhitelabelApprovalEndpoint whitelabelApprovalEndpoint;
    8. private final WhitelabelErrorEndpoint whitelabelErrorEndpoint;
    9. public AuthController(ConsumerTokenServices consumerTokenServices, TokenEndpoint tokenEndpoint,
    10. AuthorizationEndpoint authorizationRequest, WhitelabelApprovalEndpoint whitelabelApprovalEndpoint,
    11. WhitelabelErrorEndpoint whitelabelErrorEndpoint) {
    12. this.consumerTokenServices = consumerTokenServices;
    13. this.tokenEndpoint = tokenEndpoint;
    14. this.authorizationRequest = authorizationRequest;
    15. this.whitelabelApprovalEndpoint = whitelabelApprovalEndpoint;
    16. this.whitelabelErrorEndpoint = whitelabelErrorEndpoint;
    17. }
    18. /**
    19. * 自定义登录接口
    20. */
    21. @PostMapping(value = "/login")
    22. public ResponseEntity<String> login(@RequestBody UserLoginDto userLoginDto, Principal principal) throws HttpRequestMethodNotSupportedException {
    23. Map<String, String> mapDto = ObjectMapperUtil.str2Obj(userLoginDto, new TypeReference<Map<String, String>>() {
    24. });
    25. mapDto.put(GrantTypeConstants.GRANT_TYPE, GrantTypeConstants.PASSWORD);
    26. OAuth2AccessToken token;
    27. try {
    28. token = tokenEndpoint.postAccessToken(principal, mapDto).getBody();
    29. if (token == null) {
    30. throw new ServiceException("登录异常");
    31. }
    32. } catch (Exception e) {
    33. if (e instanceof InvalidGrantException) {
    34. throw new ServiceException("用户名密码错误");
    35. } else {
    36. throw e;
    37. }
    38. }
    39. return response(WebApiResponse.ok(AuthToken.build(token)));
    40. }
    41. }

    3.3:测试


    4:数据库加载client信息

    前文中以ProcessOn举例的QQ第三方登录就提到ProcessOn会向QQ申请一个client_id(客户端凭证),那么QQ第三方登录配置申请的入口必须将数据存放在数据库中,这样才能做到动态的新增、删除等,代码配置写死是不可能的。
    注意:需要引入数据源、mysql驱动的依赖,并配置好数据源。

    4.1:schema.sql

    因为官方给的sql是hql的,我用的mysql8,因此做了一些类型上的修改。

    1. SET NAMES utf8mb4;
    2. SET FOREIGN_KEY_CHECKS = 0;
    3. -- ----------------------------
    4. -- Table structure for clientdetails
    5. -- ----------------------------
    6. DROP TABLE IF EXISTS `clientdetails`;
    7. CREATE TABLE `clientdetails` (
    8. `appId` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
    9. `resourceIds` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    10. `appSecret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    11. `scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    12. `grantTypes` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    13. `redirectUrl` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    14. `authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    15. `access_token_validity` int(0) NULL DEFAULT NULL,
    16. `refresh_token_validity` int(0) NULL DEFAULT NULL,
    17. `additionalInformation` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    18. `autoApproveScopes` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    19. PRIMARY KEY (`appId`) USING BTREE
    20. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    21. -- ----------------------------
    22. -- Records of clientdetails
    23. -- ----------------------------
    24. -- ----------------------------
    25. -- Table structure for oauth_access_token
    26. -- ----------------------------
    27. DROP TABLE IF EXISTS `oauth_access_token`;
    28. CREATE TABLE `oauth_access_token` (
    29. `token_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    30. `token` binary(1) NULL DEFAULT NULL,
    31. `authentication_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
    32. `user_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    33. `client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    34. `authentication` binary(1) NULL DEFAULT NULL,
    35. `refresh_token` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    36. PRIMARY KEY (`authentication_id`) USING BTREE
    37. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    38. -- ----------------------------
    39. -- Records of oauth_access_token
    40. -- ----------------------------
    41. -- ----------------------------
    42. -- Table structure for oauth_approvals
    43. -- ----------------------------
    44. DROP TABLE IF EXISTS `oauth_approvals`;
    45. CREATE TABLE `oauth_approvals` (
    46. `userId` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    47. `clientId` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    48. `scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    49. `status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    50. `expiresAt` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0),
    51. `lastModifiedAt` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0)
    52. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    53. -- ----------------------------
    54. -- Records of oauth_approvals
    55. -- ----------------------------
    56. -- ----------------------------
    57. -- Table structure for oauth_client_details
    58. -- ----------------------------
    59. DROP TABLE IF EXISTS `oauth_client_details`;
    60. CREATE TABLE `oauth_client_details` (
    61. `client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
    62. `resource_ids` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    63. `client_secret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    64. `scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    65. `authorized_grant_types` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    66. `web_server_redirect_uri` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    67. `authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    68. `access_token_validity` int(0) NULL DEFAULT NULL,
    69. `refresh_token_validity` int(0) NULL DEFAULT NULL,
    70. `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    71. `autoapprove` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    72. PRIMARY KEY (`client_id`) USING BTREE
    73. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    74. -- ----------------------------
    75. -- Records of oauth_client_details
    76. -- ----------------------------
    77. INSERT INTO `oauth_client_details` VALUES ('admin', 'admin', 'tgMj02VG9dkpeKUN5lSWSsKotIt2yTIElkFcvsqLnwGOspNYhe+Teg==', 'test,all', 'authorization_code,client_credentials,password,implicit,refresh_token', 'http://www.baidu.com', 'admin', NULL, NULL, NULL, 'all');
    78. -- ----------------------------
    79. -- Table structure for oauth_client_token
    80. -- ----------------------------
    81. DROP TABLE IF EXISTS `oauth_client_token`;
    82. CREATE TABLE `oauth_client_token` (
    83. `token_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    84. `token` binary(1) NULL DEFAULT NULL,
    85. `authentication_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
    86. `user_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    87. `client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    88. PRIMARY KEY (`authentication_id`) USING BTREE
    89. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    90. -- ----------------------------
    91. -- Records of oauth_client_token
    92. -- ----------------------------
    93. -- ----------------------------
    94. -- Table structure for oauth_code
    95. -- ----------------------------
    96. DROP TABLE IF EXISTS `oauth_code`;
    97. CREATE TABLE `oauth_code` (
    98. `code` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    99. `authentication` binary(1) NULL DEFAULT NULL
    100. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    101. -- ----------------------------
    102. -- Records of oauth_code
    103. -- ----------------------------
    104. -- ----------------------------
    105. -- Table structure for oauth_refresh_token
    106. -- ----------------------------
    107. DROP TABLE IF EXISTS `oauth_refresh_token`;
    108. CREATE TABLE `oauth_refresh_token` (
    109. `token_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
    110. `token` binary(1) NULL DEFAULT NULL,
    111. `authentication` binary(1) NULL DEFAULT NULL
    112. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;
    113. -- ----------------------------
    114. -- Records of oauth_refresh_token
    115. -- ----------------------------
    116. SET FOREIGN_KEY_CHECKS = 1;
     
    

    在这个表中配置数据即可,参考代码静态配置:

    4.2:配置

    1. @Override
    2. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    3. clients.jdbc(dataSource);
    4. }

    做好以上配置,认证服务就可以从数据库加载客户端凭证信息了。

  • 相关阅读:
    IDEA 关闭SpringBoot启动Logo/图标
    【一】1D测量 Measuring——measure_thresh()算子
    图扑税务信息化系统管理平台,构建项目管理“一张网”
    【web-渗透测试方法】(15.7)测试功能方面的输入漏洞
    机器学习:详解朴素贝叶斯分类原理 | 例题分析 | Python实现
    GenerationMixin类
    手动模拟 calico 网络
    NTMFS4C05NT1G N-CH 30V 11.9A MOS管,PDF
    虚拟信用卡:如何获取、推荐平台及对注册开发者账号的应用
    共享变量之不可变对象与保护性拷贝!
  • 原文地址:https://blog.csdn.net/weixin_45985053/article/details/126033290