From 835d582b3b15d5c03efc37b7cd1542b2ffe22821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B7=E5=86=B7?= <2270033969@qq.com> Date: Wed, 26 Jun 2024 22:04:04 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=94=AF=E6=8C=81=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E8=A7=92=E8=89=B2=E5=8A=A8=E6=80=81=E8=84=B1=E6=95=8F?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=95=B0=E6=8D=AE=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 51541692ba7ee9ea5cf68f054f33c52133ca1a71) --- db/0pigxx_boot.sql | 1 + db/2pigxx.sql | 1 + .../PigxBootSecurityServerConfiguration.java | 158 ++++---- .../core/constant/SecurityConstants.java | 365 +++++++++--------- .../core/sensitive/SensitiveSerialize.java | 141 +++---- .../sensitive/SensitiveSkipContextHolder.java | 43 +++ .../PigxResourceServerConfiguration.java | 55 +-- .../handler/SensitiveSkipHandler.java | 42 ++ .../common/security/util/SecurityUtils.java | 112 +++--- 9 files changed, 516 insertions(+), 402 deletions(-) create mode 100644 pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSkipContextHolder.java create mode 100644 pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/SensitiveSkipHandler.java diff --git a/db/0pigxx_boot.sql b/db/0pigxx_boot.sql index c28a3f24b..c2c76e08b 100644 --- a/db/0pigxx_boot.sql +++ b/db/0pigxx_boot.sql @@ -541,6 +541,7 @@ INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `p INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2103, '审计日志', NULL, '/admin/audit/index', NULL, 2001, 'iconfont icon-biaodan', '1', 1, '0', '0', '0', '', NULL, 'admin', '2023-03-02 12:28:47', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2104, '审计记录表删除', 'sys_audit_del', NULL, NULL, 2103, '1', '1', 3, '0', NULL, '1', '', NULL, 'admin', '2023-02-28 20:23:43', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2105, '导入导出', 'sys_audit_export', NULL, NULL, 2103, '1', '1', 3, '0', NULL, '1', '', NULL, 'admin', '2023-02-28 20:23:51', '0', 1); +INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2106, '敏感数据查看', 'no_mask', NULL, NULL, 2103, '1', '1', 3, '0', NULL, '1', '', NULL, 'admin', '2023-02-28 20:23:51', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2200, '字典管理', NULL, '/admin/dict/index', NULL, 2000, 'iconfont icon-zhongduancanshuchaxun', '1', 6, '0', NULL, '0', '', '2017-11-29 11:30:52', 'admin', '2023-02-16 15:24:29', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2201, '字典删除', 'sys_dict_del', NULL, NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-29 11:30:11', ' ', '2021-05-25 03:12:55', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2202, '字典新增', 'sys_dict_add', NULL, NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:34:55', ' ', '2021-05-25 03:12:55', '0', 1); diff --git a/db/2pigxx.sql b/db/2pigxx.sql index 665bd4d84..5bed1fb1c 100644 --- a/db/2pigxx.sql +++ b/db/2pigxx.sql @@ -538,6 +538,7 @@ INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `p INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2103, '审计日志', NULL, '/admin/audit/index', NULL, 2001, 'iconfont icon-biaodan', '1', 1, '0', '0', '0', '', NULL, 'admin', '2023-03-02 12:28:47', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2104, '审计记录表删除', 'sys_audit_del', NULL, NULL, 2103, '1', '1', 3, '0', NULL, '1', '', NULL, 'admin', '2023-02-28 20:23:43', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2105, '导入导出', 'sys_audit_export', NULL, NULL, 2103, '1', '1', 3, '0', NULL, '1', '', NULL, 'admin', '2023-02-28 20:23:51', '0', 1); +INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2106, '敏感数据查看', 'no_mask', NULL, NULL, 2103, '1', '1', 3, '0', NULL, '1', '', NULL, 'admin', '2023-02-28 20:23:51', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2200, '字典管理', NULL, '/admin/dict/index', NULL, 2000, 'iconfont icon-zhongduancanshuchaxun', '1', 6, '0', NULL, '0', '', '2017-11-29 11:30:52', 'admin', '2023-02-16 15:24:29', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2201, '字典删除', 'sys_dict_del', NULL, NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-29 11:30:11', ' ', '2021-05-25 03:12:55', '0', 1); INSERT INTO `sys_menu` (`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`) VALUES (2202, '字典新增', 'sys_dict_add', NULL, NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:34:55', ' ', '2021-05-25 03:12:55', '0', 1); diff --git a/pigx-boot/src/main/java/com/pig4cloud/pigx/bootstrap/PigxBootSecurityServerConfiguration.java b/pigx-boot/src/main/java/com/pig4cloud/pigx/bootstrap/PigxBootSecurityServerConfiguration.java index 76b017673..a18d55268 100644 --- a/pigx-boot/src/main/java/com/pig4cloud/pigx/bootstrap/PigxBootSecurityServerConfiguration.java +++ b/pigx-boot/src/main/java/com/pig4cloud/pigx/bootstrap/PigxBootSecurityServerConfiguration.java @@ -30,6 +30,7 @@ import com.pig4cloud.pigx.common.core.constant.SecurityConstants; import com.pig4cloud.pigx.common.security.component.PermitAllUrlProperties; import com.pig4cloud.pigx.common.security.component.PigxBearerTokenExtractor; import com.pig4cloud.pigx.common.security.component.ResourceAuthExceptionEntryPoint; +import com.pig4cloud.pigx.common.security.handler.SensitiveSkipHandler; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -45,6 +46,7 @@ import org.springframework.security.oauth2.server.authorization.config.annotatio import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; @@ -58,102 +60,104 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @RequiredArgsConstructor public class PigxBootSecurityServerConfiguration { - private final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; + private final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; - private final OpaqueTokenIntrospector customOpaqueTokenIntrospector; + private final OpaqueTokenIntrospector customOpaqueTokenIntrospector; - private final AuthenticationConverter accessTokenRequestConverter; + private final AuthenticationConverter accessTokenRequestConverter; - private final OAuth2AuthorizationService authorizationService; + private final OAuth2AuthorizationService authorizationService; - private final PigxBearerTokenExtractor pigBearerTokenExtractor; + private final PigxBearerTokenExtractor pigBearerTokenExtractor; - private final PasswordDecoderFilter passwordDecoderFilter; + private final PasswordDecoderFilter passwordDecoderFilter; - private final OAuth2TokenGenerator oAuth2TokenGenerator; + private final OAuth2TokenGenerator oAuth2TokenGenerator; - private final ValidateCodeFilter validateCodeFilter; + private final ValidateCodeFilter validateCodeFilter; - private final PermitAllUrlProperties permitAllUrl; + private final PermitAllUrlProperties permitAllUrl; - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, PigxAuthenticationSuccessEventHandler successEventHandler, - PigxAuthenticationFailureEventHandler failureEventHandler) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, + PigxAuthenticationSuccessEventHandler successEventHandler, + PigxAuthenticationFailureEventHandler failureEventHandler) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); - // 增加验证码过滤器 - http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); - // 增加密码解密过滤器 - http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class); + // 增加验证码过滤器 + http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); + // 增加密码解密过滤器 + http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class); + http.addFilterAfter(new SensitiveSkipHandler(), BearerTokenAuthenticationFilter.class); - // 认证服务器配置 - http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点 - tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter) // 注入自定义的授权认证Converter - .accessTokenResponseHandler(successEventHandler) // 登录成功处理器 - .errorResponseHandler(failureEventHandler);// 登录失败处理器 - }).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证 - oAuth2ClientAuthenticationConfigurer.errorResponseHandler(failureEventHandler))// 处理客户端认证异常 - , Customizer.withDefaults()) - .with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 - .authorizationServerSettings( - AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build()), - Customizer.withDefaults()); + // 认证服务器配置 + http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点 + tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter) // 注入自定义的授权认证Converter + .accessTokenResponseHandler(successEventHandler) // 登录成功处理器 + .errorResponseHandler(failureEventHandler);// 登录失败处理器 + }).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证 + oAuth2ClientAuthenticationConfigurer.errorResponseHandler(failureEventHandler))// 处理客户端认证异常 + , Customizer.withDefaults()) + .with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 + .authorizationServerSettings( + AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build()), + Customizer.withDefaults()); - // 资源服务器配置 - AntPathRequestMatcher[] requestMatchers = permitAllUrl.getIgnoreUrls() - .stream() - .map(AntPathRequestMatcher::new) - .toList() - .toArray(new AntPathRequestMatcher[]{}); + // 资源服务器配置 + AntPathRequestMatcher[] requestMatchers = permitAllUrl.getIgnoreUrls() + .stream() + .map(AntPathRequestMatcher::new) + .toList() + .toArray(new AntPathRequestMatcher[] {}); - http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers) - .permitAll() - .anyRequest() - .authenticated()) - .oauth2ResourceServer( - oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector)) - .authenticationEntryPoint(resourceAuthExceptionEntryPoint) - .bearerTokenResolver(pigBearerTokenExtractor)) - .exceptionHandling(configurer -> configurer.authenticationEntryPoint(resourceAuthExceptionEntryPoint)) - .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) - .csrf(AbstractHttpConfigurer::disable); + http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers) + .permitAll() + .anyRequest() + .authenticated()) + .oauth2ResourceServer( + oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector)) + .authenticationEntryPoint(resourceAuthExceptionEntryPoint) + .bearerTokenResolver(pigBearerTokenExtractor)) + .exceptionHandling(configurer -> configurer.authenticationEntryPoint(resourceAuthExceptionEntryPoint)) + .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + .csrf(AbstractHttpConfigurer::disable); - http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 - .authorizationServerSettings( - AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build()), - Customizer.withDefaults()); + http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 + .authorizationServerSettings( + AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build()), + Customizer.withDefaults()); - DefaultSecurityFilterChain securityFilterChain = http.build(); + DefaultSecurityFilterChain securityFilterChain = http.build(); - // 注入自定义授权模式实现 - addCustomOAuth2GrantAuthenticationProvider(http); - return securityFilterChain; - } + // 注入自定义授权模式实现 + addCustomOAuth2GrantAuthenticationProvider(http); + return securityFilterChain; + } - /** - * 注入授权模式实现提供方 - *

- * 1. 密码模式
- * 2. 短信登录
- */ - @SuppressWarnings("unchecked") - private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) { - AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); - OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); + /** + * 注入授权模式实现提供方 + *

+ * 1. 密码模式
+ * 2. 短信登录
+ */ + @SuppressWarnings("unchecked") + private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) { + AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); + OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); - OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider( - authenticationManager, authorizationService, oAuth2TokenGenerator); + OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider( + authenticationManager, authorizationService, oAuth2TokenGenerator); - OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider( - authenticationManager, authorizationService, oAuth2TokenGenerator); + OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider( + authenticationManager, authorizationService, oAuth2TokenGenerator); - // 处理 UsernamePasswordAuthenticationToken - http.authenticationProvider(new PigxDaoAuthenticationProvider()); - // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken - http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); - // 处理 OAuth2ResourceOwnerSmsAuthenticationToken - http.authenticationProvider(resourceOwnerSmsAuthenticationProvider); - } + // 处理 UsernamePasswordAuthenticationToken + http.authenticationProvider(new PigxDaoAuthenticationProvider()); + // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken + http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); + // 处理 OAuth2ResourceOwnerSmsAuthenticationToken + http.authenticationProvider(resourceOwnerSmsAuthenticationProvider); + } } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java index 33e0c783b..b6c839ab1 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/constant/SecurityConstants.java @@ -25,228 +25,233 @@ package com.pig4cloud.pigx.common.core.constant; */ public interface SecurityConstants { - /** - * 启动时是否检查Inner注解安全性 - */ - boolean INNER_CHECK = true; + /** + * 启动时是否检查Inner注解安全性 + */ + boolean INNER_CHECK = true; - /** - * 刷新 - */ - String REFRESH_TOKEN = "refresh_token"; + /** + * 刷新 + */ + String REFRESH_TOKEN = "refresh_token"; - /** - * 验证码有效期 - */ - int CODE_TIME = 60; + /** + * 验证码有效期 + */ + int CODE_TIME = 60; - /** - * 验证码长度 - */ - String CODE_SIZE = "4"; + /** + * 验证码长度 + */ + String CODE_SIZE = "4"; - /** - * 角色前缀 - */ - String ROLE = "ROLE_"; + /** + * 角色前缀 + */ + String ROLE = "ROLE_"; - /** - * 前缀 - */ - String PIGX_PREFIX = "pigx_"; + /** + * 前缀 + */ + String PIGX_PREFIX = "pigx_"; - /** - * token 相关前缀 - */ - String TOKEN_PREFIX = "token:"; + /** + * token 相关前缀 + */ + String TOKEN_PREFIX = "token:"; - /** - * oauth 相关前缀 - */ - String OAUTH_PREFIX = "oauth:"; + /** + * oauth 相关前缀 + */ + String OAUTH_PREFIX = "oauth:"; - /** - * 授权码模式code key 前缀 - */ - String OAUTH_CODE_PREFIX = "oauth:code:"; + /** + * 授权码模式code key 前缀 + */ + String OAUTH_CODE_PREFIX = "oauth:code:"; - /** - * 项目的license - */ - String PIGX_LICENSE = "https://pig4cloud.com"; + /** + * 项目的license + */ + String PIGX_LICENSE = "https://pig4cloud.com"; - /** - * 内部 - */ - String FROM_IN = "Y"; + /** + * 内部 + */ + String FROM_IN = "Y"; - /** - * 标志 - */ - String FROM = "from"; + /** + * 标志 + */ + String FROM = "from"; - /** - * 请求header - */ - String HEADER_FROM_IN = FROM + "=" + FROM_IN; + /** + * 请求header + */ + String HEADER_FROM_IN = FROM + "=" + FROM_IN; - /** - * OAUTH URL - */ - String OAUTH_TOKEN_URL = "/oauth2/token"; + /** + * OAUTH URL + */ + String OAUTH_TOKEN_URL = "/oauth2/token"; - /** - * 移动端授权 - */ - String GRANT_MOBILE = "mobile"; + /** + * 移动端授权 + */ + String GRANT_MOBILE = "mobile"; - /** - * TOC 客户端 - */ - String HEADER_TOC = "CLIENT-TOC"; + /** + * TOC 客户端 + */ + String HEADER_TOC = "CLIENT-TOC"; - /** - * TOC 客户端 - */ - String HEADER_TOC_YES = "Y"; + /** + * TOC 客户端 + */ + String HEADER_TOC_YES = "Y"; - /** - * QQ获取token - */ - String QQ_AUTHORIZATION_CODE_URL = "https://graph.qq.com/oauth2.0/token?grant_type=" - + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s"; + /** + * QQ获取token + */ + String QQ_AUTHORIZATION_CODE_URL = "https://graph.qq.com/oauth2.0/token?grant_type=" + + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s"; - /** - * 微信获取OPENID - */ - String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token" - + "?appid=%s&secret=%s&code=%s&grant_type=authorization_code"; + /** + * 微信获取OPENID + */ + String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token" + + "?appid=%s&secret=%s&code=%s&grant_type=authorization_code"; - /** - * 微信小程序OPENID - */ - String MINI_APP_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/jscode2session" - + "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"; + /** + * 微信小程序OPENID + */ + String MINI_APP_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/jscode2session" + + "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"; - /** - * 码云获取token - */ - String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type=" - + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s"; + /** + * 码云获取token + */ + String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type=" + + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s"; - /** - * 开源中国获取token - */ - String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token"; + /** + * 开源中国获取token + */ + String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token"; - /** - * QQ获取用户信息 - */ - String QQ_USER_INFO_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s"; + /** + * QQ获取用户信息 + */ + String QQ_USER_INFO_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s"; - /** - * 码云获取用户信息 - */ - String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s"; + /** + * 码云获取用户信息 + */ + String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s"; - /** - * 开源中国用户信息 - */ - String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json"; + /** + * 开源中国用户信息 + */ + String OSC_USER_INFO_URL = "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json"; - /** - * 钉钉获取 token - */ - String DING_OLD_GET_TOKEN = "https://oapi.dingtalk.com/gettoken"; + /** + * 钉钉获取 token + */ + String DING_OLD_GET_TOKEN = "https://oapi.dingtalk.com/gettoken"; - /** - * 钉钉同步部门列表 - */ - String DING_OLD_DEPT_URL = "https://oapi.dingtalk.com/topapi/v2/department/listsub"; + /** + * 钉钉同步部门列表 + */ + String DING_OLD_DEPT_URL = "https://oapi.dingtalk.com/topapi/v2/department/listsub"; - /** - * 钉钉部门用户id列表 - */ - String DING_DEPT_USERIDS_URL = "https://oapi.dingtalk.com/topapi/user/listid"; + /** + * 钉钉部门用户id列表 + */ + String DING_DEPT_USERIDS_URL = "https://oapi.dingtalk.com/topapi/user/listid"; - /** - * 钉钉用户详情 - */ - String DING_USER_INFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/get"; + /** + * 钉钉用户详情 + */ + String DING_USER_INFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/get"; - /** - * {bcrypt} 加密的特征码 - */ - String BCRYPT = "{bcrypt}"; + /** + * {bcrypt} 加密的特征码 + */ + String BCRYPT = "{bcrypt}"; - /** - * 客户端模式 - */ - String CLIENT_CREDENTIALS = "client_credentials"; + /** + * 客户端模式 + */ + String CLIENT_CREDENTIALS = "client_credentials"; - /** - * 客户端编号 - */ - String CLIENT_ID = "client_id"; + /** + * 客户端编号 + */ + String CLIENT_ID = "client_id"; - /** - * 客户端唯一令牌 - */ - String CLIENT_RECREATE = "recreate_flag"; + /** + * 客户端唯一令牌 + */ + String CLIENT_RECREATE = "recreate_flag"; - /** - * 用户ID字段 - */ - String DETAILS_USER_ID = "user_id"; + /** + * 用户ID字段 + */ + String DETAILS_USER_ID = "user_id"; - /** - * 用户名 - */ - String DETAILS_USERNAME = "username"; + /** + * 用户名 + */ + String DETAILS_USERNAME = "username"; - /** - * 姓名 - */ - String NAME = "name"; + /** + * 姓名 + */ + String NAME = "name"; - /** - * 协议字段 - */ - String DETAILS_LICENSE = "license"; + /** + * 协议字段 + */ + String DETAILS_LICENSE = "license"; - /** - * 激活字段 兼容外围系统接入 - */ - String ACTIVE = "active"; + /** + * 激活字段 兼容外围系统接入 + */ + String ACTIVE = "active"; - /** - * AES 加密 - */ - String AES = "aes"; + /** + * AES 加密 + */ + String AES = "aes"; - /** - * 授权码模式confirm - */ - String CUSTOM_CONSENT_PAGE_URI = "/token/confirm_access"; + /** + * 授权码模式confirm + */ + String CUSTOM_CONSENT_PAGE_URI = "/token/confirm_access"; - /** - * {noop} 加密的特征码 - */ - String NOOP = "{noop}"; + /** + * {noop} 加密的特征码 + */ + String NOOP = "{noop}"; - /** - * 短信登录 参数名称 - */ - String SMS_PARAMETER_NAME = "mobile"; + /** + * 短信登录 参数名称 + */ + String SMS_PARAMETER_NAME = "mobile"; - /** - * 手机号登录 - */ - String APP = "mobile"; + /** + * 手机号登录 + */ + String APP = "mobile"; - /** - * 用户信息 - */ - String DETAILS_USER = "user_info"; + /** + * 用户信息 + */ + String DETAILS_USER = "user_info"; + + /** + * 不脱敏的权限 + */ + String NO_MASK = "no_mask"; } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSerialize.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSerialize.java index f45b8b94b..a0e833b4a 100644 --- a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSerialize.java +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSerialize.java @@ -41,77 +41,84 @@ import java.util.Objects; @AllArgsConstructor public class SensitiveSerialize extends JsonSerializer implements ContextualSerializer { - private SensitiveTypeEnum type; + private SensitiveTypeEnum type; - private Integer prefixNoMaskLen; + private Integer prefixNoMaskLen; - private Integer suffixNoMaskLen; + private Integer suffixNoMaskLen; - private String maskStr; + private String maskStr; - @Override - public void serialize(final String origin, final JsonGenerator jsonGenerator, - final SerializerProvider serializerProvider) throws IOException { - switch (type) { - case CHINESE_NAME: - jsonGenerator.writeString(DesensitizedUtils.chineseName(origin)); - break; - case ID_CARD: - jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin)); - break; - case FIXED_PHONE: - jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin)); - break; - case MOBILE_PHONE: - jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin)); - break; - case ADDRESS: - jsonGenerator.writeString(DesensitizedUtils.address(origin)); - break; - case EMAIL: - jsonGenerator.writeString(DesensitizedUtils.email(origin)); - break; - case BANK_CARD: - jsonGenerator.writeString(DesensitizedUtils.bankCard(origin)); - break; - case PASSWORD: - jsonGenerator.writeString(DesensitizedUtils.password(origin)); - break; - case KEY: - jsonGenerator.writeString(DesensitizedUtils.key(origin)); - break; - case IPV4: - jsonGenerator.writeString(DesensitizedUtils.ipv4(origin)); - break; - case CAR_LICENSE: - jsonGenerator.writeString(DesensitizedUtil.carLicense(origin)); - break; - case CUSTOMER: - jsonGenerator - .writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr)); - break; - default: - throw new IllegalArgumentException("Unknow sensitive type enum " + type); - } - } + @Override + public void serialize(final String origin, final JsonGenerator jsonGenerator, + final SerializerProvider serializerProvider) throws IOException { - @Override - public JsonSerializer createContextual(final SerializerProvider serializerProvider, - final BeanProperty beanProperty) throws JsonMappingException { - if (beanProperty != null) { - if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { - Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class); - if (sensitive == null) { - sensitive = beanProperty.getContextAnnotation(Sensitive.class); - } - if (sensitive != null) { - return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(), - sensitive.suffixNoMaskLen(), sensitive.maskStr()); - } - } - return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); - } - return serializerProvider.findNullValueSerializer(null); - } + // 跳过脱敏 + if (SensitiveSkipContextHolder.getSkip()) { + jsonGenerator.writeString(origin); + return; + } + + switch (type) { + case CHINESE_NAME: + jsonGenerator.writeString(DesensitizedUtils.chineseName(origin)); + break; + case ID_CARD: + jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin)); + break; + case FIXED_PHONE: + jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin)); + break; + case MOBILE_PHONE: + jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin)); + break; + case ADDRESS: + jsonGenerator.writeString(DesensitizedUtils.address(origin)); + break; + case EMAIL: + jsonGenerator.writeString(DesensitizedUtils.email(origin)); + break; + case BANK_CARD: + jsonGenerator.writeString(DesensitizedUtils.bankCard(origin)); + break; + case PASSWORD: + jsonGenerator.writeString(DesensitizedUtils.password(origin)); + break; + case KEY: + jsonGenerator.writeString(DesensitizedUtils.key(origin)); + break; + case IPV4: + jsonGenerator.writeString(DesensitizedUtils.ipv4(origin)); + break; + case CAR_LICENSE: + jsonGenerator.writeString(DesensitizedUtil.carLicense(origin)); + break; + case CUSTOMER: + jsonGenerator + .writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr)); + break; + default: + throw new IllegalArgumentException("Unknow sensitive type enum " + type); + } + } + + @Override + public JsonSerializer createContextual(final SerializerProvider serializerProvider, + final BeanProperty beanProperty) throws JsonMappingException { + if (beanProperty != null) { + if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { + Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class); + if (sensitive == null) { + sensitive = beanProperty.getContextAnnotation(Sensitive.class); + } + if (sensitive != null) { + return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(), + sensitive.suffixNoMaskLen(), sensitive.maskStr()); + } + } + return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); + } + return serializerProvider.findNullValueSerializer(null); + } } diff --git a/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSkipContextHolder.java b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSkipContextHolder.java new file mode 100644 index 000000000..67f6a9c0a --- /dev/null +++ b/pigx-common/pigx-common-core/src/main/java/com/pig4cloud/pigx/common/core/sensitive/SensitiveSkipContextHolder.java @@ -0,0 +1,43 @@ +package com.pig4cloud.pigx.common.core.sensitive; + +import com.alibaba.ttl.TransmittableThreadLocal; +import lombok.experimental.UtilityClass; + +import java.util.Objects; + +/** + * 脱密跳过ttl + * + * @author lengleng + * @date 2024/6/26 + */ +@UtilityClass +public class SensitiveSkipContextHolder { + + private final ThreadLocal THREAD_LOCAL_SKIP = new TransmittableThreadLocal<>(); + + /** + * 设置是否跳过脱敏 + * + * @param skip 是否跳过 + */ + public void setSkip(Boolean skip) { + THREAD_LOCAL_SKIP.set(skip); + } + + /** + * 获取是否跳过脱敏 + * + * @return 是否跳过 + */ + public Boolean getSkip() { + return Objects.isNull(THREAD_LOCAL_SKIP.get()) ? Boolean.FALSE : THREAD_LOCAL_SKIP.get(); + } + + /** + * 清除 + */ + public void clear() { + THREAD_LOCAL_SKIP.remove(); + } +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java index e5da1b8ac..88a91266e 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java @@ -16,6 +16,7 @@ package com.pig4cloud.pigx.common.security.component; +import com.pig4cloud.pigx.common.security.handler.SensitiveSkipHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -27,13 +28,14 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; +import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; /** * @author lengleng * @date 2022-06-04 - * + *

* 资源服务器认证授权配置 */ @Slf4j @@ -42,35 +44,38 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @RequiredArgsConstructor public class PigxResourceServerConfiguration { - protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; + protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; - private final PermitAllUrlProperties permitAllUrl; + private final PermitAllUrlProperties permitAllUrl; - private final PigxBearerTokenExtractor pigxBearerTokenExtractor; + private final PigxBearerTokenExtractor pigxBearerTokenExtractor; - private final OpaqueTokenIntrospector customOpaqueTokenIntrospector; + private final OpaqueTokenIntrospector customOpaqueTokenIntrospector; - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - AntPathRequestMatcher[] requestMatchers = permitAllUrl.getIgnoreUrls() - .stream() - .map(AntPathRequestMatcher::new) - .toList() - .toArray(new AntPathRequestMatcher[] {}); + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + AntPathRequestMatcher[] requestMatchers = permitAllUrl.getIgnoreUrls() + .stream() + .map(AntPathRequestMatcher::new) + .toList() + .toArray(new AntPathRequestMatcher[]{}); - http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers) - .permitAll() - .anyRequest() - .authenticated()) - .oauth2ResourceServer( - oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector)) - .authenticationEntryPoint(resourceAuthExceptionEntryPoint) - .bearerTokenResolver(pigxBearerTokenExtractor)) - .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) - .csrf(AbstractHttpConfigurer::disable); + // 增加输出脱敏的处理 + http.addFilterAfter(new SensitiveSkipHandler(), BearerTokenAuthenticationFilter.class); - return http.build(); - } + http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(requestMatchers) + .permitAll() + .anyRequest() + .authenticated()) + .oauth2ResourceServer( + oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector)) + .authenticationEntryPoint(resourceAuthExceptionEntryPoint) + .bearerTokenResolver(pigxBearerTokenExtractor)) + .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) + .csrf(AbstractHttpConfigurer::disable); + + return http.build(); + } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/SensitiveSkipHandler.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/SensitiveSkipHandler.java new file mode 100644 index 000000000..bacdd9eb7 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/handler/SensitiveSkipHandler.java @@ -0,0 +1,42 @@ +package com.pig4cloud.pigx.common.security.handler; + +import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.core.sensitive.SensitiveSkipContextHolder; +import com.pig4cloud.pigx.common.security.service.PigxUser; +import com.pig4cloud.pigx.common.security.util.SecurityUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * 敏感词跳过的处理器 + * + * @author lengleng + * @date 2024/6/26 + */ +public class SensitiveSkipHandler extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + PigxUser user = SecurityUtils.getUser(); + + if (user == null) { + filterChain.doFilter(request, response); + return; + } + + try { + if (user.getAuthorities().stream().map(GrantedAuthority::getAuthority) + .toList().contains(SecurityConstants.NO_MASK)) { + SensitiveSkipContextHolder.setSkip(true); + } + filterChain.doFilter(request, response); + } finally { + SensitiveSkipContextHolder.clear(); + } + } +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/SecurityUtils.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/SecurityUtils.java index d9312784a..205d6c570 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/SecurityUtils.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/util/SecurityUtils.java @@ -38,63 +38,69 @@ import java.util.List; @UtilityClass public class SecurityUtils { - /** - * 获取Authentication - */ - public Authentication getAuthentication() { - return SecurityContextHolder.getContext().getAuthentication(); - } + /** + * 获取Authentication + */ + public Authentication getAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } - /** - * 获取Authentication Token - * @return - */ - public String getToken(){ - Authentication authentication = SecurityUtils.getAuthentication(); - if (authentication instanceof BearerTokenAuthentication bearerTokenAuthentication) { - return bearerTokenAuthentication.getToken().getTokenValue(); - } - return null; - } + /** + * 获取Authentication Token + * + * @return + */ + public String getToken() { + Authentication authentication = SecurityUtils.getAuthentication(); + if (authentication instanceof BearerTokenAuthentication bearerTokenAuthentication) { + return bearerTokenAuthentication.getToken().getTokenValue(); + } + return null; + } - /** - * 获取用户 - * @param authentication - * @return PigxUser - *

- */ - public PigxUser getUser(Authentication authentication) { - Object principal = authentication.getPrincipal(); - if (principal instanceof PigxUser) { - return (PigxUser) principal; - } - return null; - } + /** + * 获取用户 + * + * @param authentication + * @return PigxUser + *

+ */ + public PigxUser getUser(Authentication authentication) { + if (authentication == null) { + return null; + } + Object principal = authentication.getPrincipal(); + if (principal instanceof PigxUser) { + return (PigxUser) principal; + } + return null; + } - /** - * 获取用户 - */ - public PigxUser getUser() { - Authentication authentication = getAuthentication(); - return getUser(authentication); - } + /** + * 获取用户 + */ + public PigxUser getUser() { + Authentication authentication = getAuthentication(); + return getUser(authentication); + } - /** - * 获取用户角色信息 - * @return 角色集合 - */ - public List getRoleIds() { - Authentication authentication = getAuthentication(); - Collection authorities = authentication.getAuthorities(); + /** + * 获取用户角色信息 + * + * @return 角色集合 + */ + public List getRoleIds() { + Authentication authentication = getAuthentication(); + Collection authorities = authentication.getAuthorities(); - List roleIds = new ArrayList<>(); - authorities.stream() - .filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE)) - .forEach(granted -> { - String id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE); - roleIds.add(Long.parseLong(id)); - }); - return roleIds; - } + List roleIds = new ArrayList<>(); + authorities.stream() + .filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE)) + .forEach(granted -> { + String id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE); + roleIds.add(Long.parseLong(id)); + }); + return roleIds; + } }