✨ 支持根据角色动态脱敏指定数据属性

(cherry picked from commit 51541692ba7ee9ea5cf68f054f33c52133ca1a71)
This commit is contained in:
冷冷 committed 2024-08-16 15:16:06 +08:00
1 parent 69aa10d54e
commit 835d582b3b
9 files changed
+516 -402

No files matched your search

+1
View File
@@ -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);
+1
View File
@@ -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);
@@ -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;
}
/**
* 注入授权模式实现提供方
* <p>
* 1. 密码模式 </br>
* 2. 短信登录 </br>
*/
@SuppressWarnings("unchecked")
private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
/**
* 注入授权模式实现提供方
* <p>
* 1. 密码模式 </br>
* 2. 短信登录 </br>
*/
@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);
}
}
@@ -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";
}
@@ -41,77 +41,84 @@ import java.util.Objects;
@AllArgsConstructor
public class SensitiveSerialize extends JsonSerializer<String> 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);
}
}
@@ -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<Boolean> 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();
}
}
@@ -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
*
* <p>
* 资源服务器认证授权配置
*/
@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();
}
}
@@ -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();
}
}
}
@@ -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
* <p>
*/
public PigxUser getUser(Authentication authentication) {
Object principal = authentication.getPrincipal();
if (principal instanceof PigxUser) {
return (PigxUser) principal;
}
return null;
}
/**
* 获取用户
*
* @param authentication
* @return PigxUser
* <p>
*/
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<Long> getRoleIds() {
Authentication authentication = getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
/**
* 获取用户角色信息
*
* @return 角色集合
*/
public List<Long> getRoleIds() {
Authentication authentication = getAuthentication();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<Long> 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<Long> 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;
}
}