1、结构调整
2、抽象工厂优化
This commit is contained in:
@@ -2,12 +2,13 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>msgpush-spring-boot-starter-security</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -68,5 +69,4 @@
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.njcn.msgpush.framework.operatelog.config;
|
||||
|
||||
import com.njcn.msgpush.framework.operatelog.core.service.LogRecordServiceImpl;
|
||||
import com.mzt.logapi.service.ILogRecordService;
|
||||
import com.mzt.logapi.starter.annotation.EnableLogRecord;
|
||||
import com.njcn.msgpush.framework.operatelog.core.service.LogRecordServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -11,9 +11,9 @@ import org.springframework.context.annotation.Primary;
|
||||
/**
|
||||
* 操作日志配置类
|
||||
*
|
||||
* @author HUIHUI
|
||||
* @author hongawen
|
||||
*/
|
||||
@EnableLogRecord(tenant = "admin") // 貌似用不上 tenant 这玩意给个空好啦
|
||||
@EnableLogRecord(tenant = "") // 貌似用不上 tenant 这玩意给个空好啦
|
||||
@AutoConfiguration
|
||||
@Slf4j
|
||||
public class MsgpushOperateLogConfiguration {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package com.njcn.msgpush.framework.operatelog.core.service;
|
||||
|
||||
import com.mzt.logapi.beans.LogRecord;
|
||||
import com.mzt.logapi.service.ILogRecordService;
|
||||
import com.njcn.msgpush.framework.common.biz.system.logger.OperateLogCommonApi;
|
||||
import com.njcn.msgpush.framework.common.biz.system.logger.dto.OperateLogCreateReqDTO;
|
||||
import com.njcn.msgpush.framework.common.util.monitor.TracerUtils;
|
||||
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
|
||||
import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.msgpush.framework.common.biz.system.logger.dto.OperateLogCreateReqDTO;
|
||||
import com.mzt.logapi.beans.LogRecord;
|
||||
import com.mzt.logapi.service.ILogRecordService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -20,7 +19,7 @@ import java.util.List;
|
||||
*
|
||||
* 基于 {@link OperateLogCommonApi} 实现,记录操作日志
|
||||
*
|
||||
* @author HUIHUI
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
public class LogRecordServiceImpl implements ILogRecordService {
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
* 基于 mzt-log 框架
|
||||
* 实现操作日志功能
|
||||
*
|
||||
* @author HUIHUI
|
||||
* @author hongawen
|
||||
*/
|
||||
package com.njcn.msgpush.framework.operatelog;
|
||||
|
||||
@@ -6,7 +6,6 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 自定义的 URL 的安全配置
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.msgpush.framework.security.config;
|
||||
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import com.njcn.msgpush.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import com.njcn.msgpush.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
|
||||
import com.njcn.msgpush.framework.security.core.filter.TokenAuthenticationFilter;
|
||||
@@ -8,7 +9,6 @@ import com.njcn.msgpush.framework.security.core.handler.AuthenticationEntryPoint
|
||||
import com.njcn.msgpush.framework.security.core.service.SecurityFrameworkService;
|
||||
import com.njcn.msgpush.framework.security.core.service.SecurityFrameworkServiceImpl;
|
||||
import com.njcn.msgpush.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
@@ -38,7 +38,7 @@ public class MsgpushSecurityAutoConfiguration {
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
/**
|
||||
* 身份认证失败处理类 Bean
|
||||
* 认证失败处理类 Bean
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationEntryPoint authenticationEntryPoint() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.njcn.msgpush.framework.security.config;
|
||||
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import com.njcn.msgpush.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import com.njcn.msgpush.framework.security.core.rpc.LoginUserRequestInterceptor;
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.njcn.msgpush.framework.security.config;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.njcn.msgpush.framework.security.core.filter.TokenAuthenticationFilter;
|
||||
import com.njcn.msgpush.framework.web.config.WebProperties;
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.njcn.msgpush.framework.security.core.filter.TokenAuthenticationFilter;
|
||||
import com.njcn.msgpush.framework.web.config.WebProperties;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
@@ -114,6 +114,8 @@ public class MsgpushWebSecurityConfigurerAdapter {
|
||||
.cors(Customizer.withDefaults())
|
||||
// CSRF 禁用,因为不使用 Session
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
// 基于 token 机制,所以不需要 Session
|
||||
.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.njcn.msgpush.framework.security.config;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.njcn.msgpush.framework.security.core;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -10,47 +9,18 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Data
|
||||
public class LoginUser {
|
||||
|
||||
public static final String INFO_KEY_NICKNAME = "nickname";
|
||||
public static final String INFO_KEY_DEPT_ID = "deptId";
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 用户类型
|
||||
*
|
||||
* 关联 {@link UserTypeEnum}
|
||||
*/
|
||||
private Integer userType;
|
||||
/**
|
||||
* 额外的用户信息
|
||||
*/
|
||||
private Map<String, String> info;
|
||||
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
private List<String> scopes;
|
||||
/**
|
||||
* 过期时间
|
||||
*/
|
||||
private LocalDateTime expiresTime;
|
||||
|
||||
// ========== 上下文 ==========
|
||||
/**
|
||||
* 上下文字段,不进行持久化
|
||||
*
|
||||
* 1. 用于基于 LoginUser 维度的临时缓存
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Map<String, Object> context;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.springframework.util.Assert;
|
||||
public class TransmittableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
|
||||
|
||||
/**
|
||||
* 使用 TransmittableThreadLocal 实现线程之间上下文的传递。
|
||||
* 使用 TransmittableThreadLocal 作为上下文
|
||||
*/
|
||||
private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ package com.njcn.msgpush.framework.security.core.filter;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import com.njcn.msgpush.framework.common.exception.ServiceException;
|
||||
import com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
|
||||
@@ -11,8 +14,6 @@ import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.msgpush.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
|
||||
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -26,42 +27,29 @@ import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Token 过滤器,验证 token 的有效性
|
||||
* 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
private final GlobalExceptionHandler globalExceptionHandler;
|
||||
|
||||
private final OAuth2TokenCommonApi oauth2TokenApi;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("NullableProblems")
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
// 情况一,基于 header[login-user] 获得用户,例如说来自 Gateway 或者其它服务透传
|
||||
LoginUser loginUser = buildLoginUserByHeader(request);
|
||||
|
||||
// 情况二,基于 Token 获得用户
|
||||
// 注意,这里主要满足直接使用 Nginx 直接转发到 Spring Cloud 服务的场景。
|
||||
if (loginUser == null) {
|
||||
String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
||||
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
||||
if (StrUtil.isNotEmpty(token)) {
|
||||
Integer userType = WebFrameworkUtils.getLoginUserType(request);
|
||||
try {
|
||||
// 1.1 基于 token 构建登录用户
|
||||
loginUser = buildLoginUserByToken(token, userType);
|
||||
// 1.2 模拟 Login 功能,方便日常开发调试
|
||||
if (loginUser == null) {
|
||||
loginUser = mockLoginUser(request, token, userType);
|
||||
loginUser = mockLoginUser(token, userType);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
|
||||
@@ -71,57 +59,41 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前用户
|
||||
if (loginUser != null) {
|
||||
SecurityFrameworkUtils.setLoginUser(loginUser, request);
|
||||
}
|
||||
// 继续过滤链
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private LoginUser buildLoginUserByToken(String token, Integer userType) {
|
||||
try {
|
||||
// 校验访问令牌
|
||||
OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData();
|
||||
if (accessToken == null) {
|
||||
return null;
|
||||
}
|
||||
// 用户类型不匹配,无权限
|
||||
// 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型
|
||||
// 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的
|
||||
if (userType != null
|
||||
&& ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
|
||||
if (userType != null && ObjectUtil.notEqual(accessToken.getUserType(), userType)) {
|
||||
throw new AccessDeniedException("错误的用户类型");
|
||||
}
|
||||
// 构建登录用户
|
||||
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
|
||||
.setInfo(accessToken.getUserInfo()) // 额外的用户信息
|
||||
return new LoginUser().setId(accessToken.getUserId())
|
||||
.setUserType(accessToken.getUserType())
|
||||
.setInfo(accessToken.getUserInfo())
|
||||
.setScopes(accessToken.getScopes())
|
||||
.setExpiresTime(accessToken.getExpiresTime());
|
||||
} catch (ServiceException serviceException) {
|
||||
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
|
||||
return null;
|
||||
if (ObjectUtil.equal(serviceException.getCode(), GlobalErrorCodeConstants.UNAUTHORIZED.getCode())) {
|
||||
return null;
|
||||
}
|
||||
throw serviceException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟登录用户,方便日常开发调试
|
||||
*
|
||||
* 注意,在线上环境下,一定要关闭该功能!!!
|
||||
*
|
||||
* @param request 请求
|
||||
* @param token 模拟的 token,格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
|
||||
* @param userType 用户类型
|
||||
* @return 模拟的 LoginUser
|
||||
*/
|
||||
private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
|
||||
private LoginUser mockLoginUser(String token, Integer userType) {
|
||||
if (!securityProperties.getMockEnable()) {
|
||||
return null;
|
||||
}
|
||||
// 必须以 mockSecret 开头
|
||||
if (!token.startsWith(securityProperties.getMockSecret())) {
|
||||
return null;
|
||||
}
|
||||
// 构建模拟用户
|
||||
Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
|
||||
return new LoginUser().setId(userId).setUserType(userType);
|
||||
}
|
||||
@@ -132,20 +104,15 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8); // 解码,解决中文乱码问题
|
||||
loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8);
|
||||
LoginUser loginUser = JsonUtils.parseObject(loginUserStr, LoginUser.class);
|
||||
// 用户类型不匹配,无权限
|
||||
// 注意:只有 /admin-api/* 和 /app-api/* 有 userType,才需要比对用户类型
|
||||
// 类似 WebSocket 的 /ws/* 连接地址,是不需要比对用户类型的
|
||||
Integer userType = WebFrameworkUtils.getLoginUserType(request);
|
||||
if (userType != null
|
||||
&& loginUser != null
|
||||
&& ObjectUtil.notEqual(loginUser.getUserType(), userType)) {
|
||||
if (userType != null && loginUser != null && ObjectUtil.notEqual(loginUser.getUserType(), userType)) {
|
||||
throw new AccessDeniedException("错误的用户类型");
|
||||
}
|
||||
return loginUser;
|
||||
} catch (Exception ex) {
|
||||
log.error("[buildLoginUserByHeader][解析 LoginUser({}) 发生异常]", loginUserStr, ex); ;
|
||||
log.error("[buildLoginUserByHeader][parse LoginUser({}) error]", loginUserStr, ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ package com.njcn.msgpush.framework.security.core.handler;
|
||||
|
||||
import com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
|
||||
|
||||
@@ -3,15 +3,14 @@ package com.njcn.msgpush.framework.security.core.handler;
|
||||
import com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;
|
||||
|
||||
/**
|
||||
@@ -19,7 +18,7 @@ import static com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeC
|
||||
*
|
||||
* 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
|
||||
*
|
||||
* @author ruoyi
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
@SuppressWarnings("JavadocReference") // 忽略文档引用报错
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.njcn.msgpush.framework.security.core.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.njcn.msgpush.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import com.njcn.msgpush.framework.common.core.KeyValue;
|
||||
import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@@ -17,42 +17,27 @@ import java.util.List;
|
||||
import static com.njcn.msgpush.framework.common.util.cache.CacheUtils.buildCache;
|
||||
import static com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
|
||||
/**
|
||||
* 默认的 {@link SecurityFrameworkService} 实现类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
|
||||
private final PermissionCommonApi permissionApi;
|
||||
|
||||
/**
|
||||
* 针对 {@link #hasAnyRoles(String...)} 的缓存
|
||||
*/
|
||||
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyRolesCache = buildCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
Duration.ofMinutes(1L),
|
||||
new CacheLoader<KeyValue<Long, List<String>>, Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean load(KeyValue<Long, List<String>> key) {
|
||||
return permissionApi.hasAnyRoles(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* 针对 {@link #hasAnyPermissions(String...)} 的缓存
|
||||
*/
|
||||
private final LoadingCache<KeyValue<Long, List<String>>, Boolean> hasAnyPermissionsCache = buildCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
Duration.ofMinutes(1L),
|
||||
new CacheLoader<KeyValue<Long, List<String>>, Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean load(KeyValue<Long, List<String>> key) {
|
||||
return permissionApi.hasAnyPermissions(key.getKey(), key.getValue().toArray(new String[0])).getCheckedData();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Override
|
||||
@@ -63,7 +48,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasAnyPermissions(String... permissions) {
|
||||
// 权限校验
|
||||
Long userId = getLoginUserId();
|
||||
if (userId == null) {
|
||||
return false;
|
||||
@@ -79,7 +63,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasAnyRoles(String... roles) {
|
||||
// 权限校验
|
||||
Long userId = getLoginUserId();
|
||||
if (userId == null) {
|
||||
return false;
|
||||
@@ -94,7 +77,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
|
||||
@Override
|
||||
public boolean hasAnyScopes(String... scope) {
|
||||
// 权限校验
|
||||
LoginUser user = SecurityFrameworkUtils.getLoginUser();
|
||||
if (user == null) {
|
||||
return false;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.njcn.msgpush.framework.security.core.util;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -13,36 +13,16 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 安全服务工具类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class SecurityFrameworkUtils {
|
||||
|
||||
/**
|
||||
* HEADER 认证头 value 的前缀
|
||||
*/
|
||||
public static final String AUTHORIZATION_BEARER = "Bearer";
|
||||
|
||||
public static final String LOGIN_USER_HEADER = "login-user";
|
||||
|
||||
private SecurityFrameworkUtils() {}
|
||||
|
||||
/**
|
||||
* 从请求中,获得认证 Token
|
||||
*
|
||||
* @param request 请求
|
||||
* @param headerName 认证 Token 对应的 Header 名字
|
||||
* @param parameterName 认证 Token 对应的 Parameter 名字
|
||||
* @return 认证 Token
|
||||
*/
|
||||
public static String obtainAuthorization(HttpServletRequest request,
|
||||
String headerName, String parameterName) {
|
||||
// 1. 获得 Token。优先级:Header > Parameter
|
||||
public static String obtainAuthorization(HttpServletRequest request, String headerName, String parameterName) {
|
||||
String token = request.getHeader(headerName);
|
||||
if (StrUtil.isEmpty(token)) {
|
||||
token = request.getParameter(parameterName);
|
||||
@@ -50,16 +30,10 @@ public class SecurityFrameworkUtils {
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return null;
|
||||
}
|
||||
// 2. 去除 Token 中带的 Bearer
|
||||
int index = token.indexOf(AUTHORIZATION_BEARER + " ");
|
||||
return index >= 0 ? token.substring(index + 7).trim() : token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前认证信息
|
||||
*
|
||||
* @return 认证信息
|
||||
*/
|
||||
public static Authentication getAuthentication() {
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
if (context == null) {
|
||||
@@ -68,11 +42,6 @@ public class SecurityFrameworkUtils {
|
||||
return context.getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*
|
||||
* @return 当前用户
|
||||
*/
|
||||
@Nullable
|
||||
public static LoginUser getLoginUser() {
|
||||
Authentication authentication = getAuthentication();
|
||||
@@ -82,52 +51,28 @@ public class SecurityFrameworkUtils {
|
||||
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的编号,从上下文中
|
||||
*
|
||||
* @return 用户编号
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getLoginUserId() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? loginUser.getId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的昵称,从上下文中
|
||||
*
|
||||
* @return 昵称
|
||||
*/
|
||||
@Nullable
|
||||
public static String getLoginUserNickname() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? MapUtil.getStr(loginUser.getInfo(), LoginUser.INFO_KEY_NICKNAME) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的部门编号,从上下文中
|
||||
*
|
||||
* @return 部门编号
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getLoginUserDeptId() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前用户
|
||||
*
|
||||
* @param loginUser 登录用户
|
||||
* @param request 请求
|
||||
*/
|
||||
public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
|
||||
// 创建 Authentication,并设置到上下文
|
||||
Authentication authentication = buildAuthentication(loginUser, request);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
// 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
|
||||
// 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
|
||||
if (request != null) {
|
||||
WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
|
||||
WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
|
||||
@@ -135,12 +80,10 @@ public class SecurityFrameworkUtils {
|
||||
}
|
||||
|
||||
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
|
||||
// 创建 UsernamePasswordAuthenticationToken 对象
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
||||
loginUser, null, Collections.emptyList());
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ com.njcn.msgpush.framework.security.config.MsgpushSecurityRpcAutoConfiguration
|
||||
com.njcn.msgpush.framework.security.config.MsgpushSecurityAutoConfiguration
|
||||
com.njcn.msgpush.framework.security.config.MsgpushWebSecurityConfigurerAdapter
|
||||
com.njcn.msgpush.framework.operatelog.config.MsgpushOperateLogConfiguration
|
||||
com.njcn.msgpush.framework.operatelog.config.MsgpushOperateLogRpcAutoConfiguration
|
||||
com.njcn.msgpush.framework.operatelog.config.MsgpushOperateLogRpcAutoConfiguration
|
||||
|
||||
Reference in New Issue
Block a user