Revert "!22 sa-token"

This reverts commit e465fdbf0a, reversing
changes made to c6d7ae9f46.
This commit is contained in:
zhuoda
2023-07-26 21:18:39 +08:00
parent e465fdbf0a
commit 25aad6ba5f
74 changed files with 1240 additions and 746 deletions

View File

@@ -49,19 +49,12 @@
</exclusions>
</dependency>
<!-- sa-token start -->
<!-- spring security -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
</dependency>
<!-- sa-token end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>

View File

@@ -0,0 +1,22 @@
package net.lab1024.sa.common.common.annoation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 校验权限注解
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-05-30 21:22:12
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SaAuth {
String saAuth = "saAuth";
}

View File

@@ -24,7 +24,7 @@ public enum UserErrorCode implements ErrorCode {
REPEAT_SUBMIT(30004, "亲~您操作的太快了,请稍等下再操作~"),
NO_PERMISSION(30005, "对不起,您无法访问此内容哦~"),
NO_PERMISSION(30005, "对不起,您无法访问此资源哦~"),
DEVELOPING(30006, "系統正在紧急开发中,敬请期待~"),
@@ -32,11 +32,7 @@ public enum UserErrorCode implements ErrorCode {
USER_STATUS_ERROR(30008, "用户状态异常"),
FORM_REPEAT_SUBMIT(30009, "请勿重复提交"),
LOGIN_FROM_OTHER(30010, "您的账号已在其他地方登录"),
;
FORM_REPEAT_SUBMIT(30009, "请勿重复提交");
private final int code;

View File

@@ -26,11 +26,6 @@ public class StringConst {
*/
public static final String HORIZONTAL = "-";
/**
* 全局通用 冒号
*/
public static final String COLON = ":";
/**
* 全局通用分隔符
*/

View File

@@ -18,6 +18,9 @@ public class RequestUrlVO {
@ApiModelProperty("注释说明")
private String comment;
@ApiModelProperty("controller.method")
private String name;
@ApiModelProperty("url")
private String url;
}

View File

@@ -1,11 +1,9 @@
package net.lab1024.sa.common.common.domain;
import lombok.Data;
import net.lab1024.sa.common.common.enumeration.UserTypeEnum;
/**
* 请求用户
* 多系统用户 可以继承此类
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-12-21 19:55:07
@@ -13,25 +11,39 @@ import net.lab1024.sa.common.common.enumeration.UserTypeEnum;
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Data
public class RequestUser {
public interface RequestUser {
/**
* 当前请求用户id
* 请求用户id
*
* @return
*/
private Long userId;
Long getUserId();
/**
* 当前请求用户名称
* 请求用户名称
*
* @return
*/
private String userName;
String getUserName();
/**
* 当前请求用户类型
* 获取用户类型
*/
private UserTypeEnum userType;
UserTypeEnum getUserType();
private String ip;
/**
* 获取请求的IP
*
* @return
*/
String getIp();
/**
* 获取请求 user-agent
*
* @return
*/
String getUserAgent();
private String userAgent;
}

View File

@@ -3,7 +3,7 @@ package net.lab1024.sa.common.common.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.lab1024.sa.common.common.enumeration.SystemEnvEnum;
import net.lab1024.sa.common.common.enumeration.SystemEnvironmentEnum;
/**
* 系统环境
@@ -16,7 +16,7 @@ import net.lab1024.sa.common.common.enumeration.SystemEnvEnum;
*/
@AllArgsConstructor
@Getter
public class SystemEnv {
public class SystemEnvironment {
/**
* 是否位生产环境
@@ -31,5 +31,5 @@ public class SystemEnv {
/**
* 当前环境
*/
private SystemEnvEnum currentEnv;
private SystemEnvironmentEnum currentEnvironment;
}

View File

@@ -15,32 +15,32 @@ import lombok.Getter;
*/
@AllArgsConstructor
@Getter
public enum SystemEnvEnum implements BaseEnum {
public enum SystemEnvironmentEnum implements BaseEnum {
/**
* dev
*/
DEV(EnvConst.DEV, "开发环境"),
DEV(SystemEnvironmentNameConst.DEV, "开发环境"),
/**
* test
*/
TEST(EnvConst.TEST, "测试环境"),
TEST(SystemEnvironmentNameConst.TEST, "测试环境"),
/**
* pre
*/
PRE(EnvConst.PRE, "预发布环境"),
PRE(SystemEnvironmentNameConst.PRE, "预发布环境"),
/**
* prod
*/
PROD(EnvConst.PROD, "生产环境");
PROD(SystemEnvironmentNameConst.PROD, "生产环境");
private final String value;
private final String desc;
public static final class EnvConst {
public static final class SystemEnvironmentNameConst {
public static final String DEV = "dev";
public static final String TEST = "test";
public static final String PRE = "pre";

View File

@@ -1,8 +1,5 @@
package net.lab1024.sa.common.common.enumeration;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户类型
*
@@ -12,16 +9,29 @@ import lombok.Getter;
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net 2012-2022
*/
@Getter
@AllArgsConstructor
public enum UserTypeEnum implements BaseEnum {
/**
* 管理端 员工用户
*/
ADMIN_EMPLOYEE(1, "管理端-员工");
ADMIN_EMPLOYEE(1, "员工");
private final Integer value;
private Integer type;
private final String desc;
private String desc;
UserTypeEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
@Override
public Integer getValue() {
return type;
}
@Override
public String getDesc() {
return desc;
}
}

View File

@@ -1,27 +1,17 @@
package net.lab1024.sa.common.common.interceptor;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import net.lab1024.sa.common.common.annoation.NoNeedLogin;
import net.lab1024.sa.common.common.code.UserErrorCode;
import net.lab1024.sa.common.common.constant.RequestHeaderConst;
import net.lab1024.sa.common.common.domain.RequestUser;
import net.lab1024.sa.common.common.domain.ResponseDTO;
import net.lab1024.sa.common.common.domain.SystemEnv;
import net.lab1024.sa.common.common.enumeration.SystemEnvEnum;
import net.lab1024.sa.common.common.enumeration.UserTypeEnum;
import net.lab1024.sa.common.common.util.SmartRequestUtil;
import net.lab1024.sa.common.module.support.token.TokenService;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
@@ -29,56 +19,48 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
/**
* 抽象拦截器
* 只校验了登录处理
* 自定义的拦截器 可以继承此类
*
* @author huke
* @date 2023-07-12 21:56:14
* @Author 1024创新实验室: 罗伊
* @Date 2021-10-09 20:56:14
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
public abstract class AbstractInterceptor implements HandlerInterceptor {
@Autowired
private SystemEnv systemEnv;
private List<String> ignoreUrlList;
/**
* 校验 token
* Token获取用户信息
*
* @param userId
* @return
*/
public abstract RequestUser getDevUser(Long userId);
/**
* 校验 当前服务用户类型
*/
public abstract UserTypeEnum getUserType();
protected abstract Function<String, RequestUser> userFunction();
/**
* 拦截路径
*
* @return
*/
public abstract List<String> pathPatterns();
public abstract String[] pathPatterns();
/**
* 忽略的url集合
*
* @return
*/
public List<String> getIgnoreUrlList() {
List<String> ignoreUrlList = Lists.newArrayList();
ignoreUrlList.add("/swagger-ui.html");
ignoreUrlList.add("/swagger-resources/**");
ignoreUrlList.add("/webjars/**");
ignoreUrlList.add("/druid/**");
ignoreUrlList.add("/*/api-docs");
protected List<String> getIgnoreUrlList() {
return ignoreUrlList;
}
/**
* 拦截处理登录 token
* 拦截服务器端响应处理ajax请求返回结果
*
* @param request
* @param response
@@ -89,81 +71,60 @@ public abstract class AbstractInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// OPTIONS请求直接return
if (StringUtils.equalsIgnoreCase(HttpMethod.OPTIONS.name(), request.getMethod())) {
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.NO_CONTENT.value());
return false;
}
boolean isHandler = handler instanceof HandlerMethod;
if (!isHandler) {
return true;
}
// 校验 token
ResponseDTO<RequestUser> res = this.checkTokenAndGetUser(request);
if (res.getOk()) {
SmartRequestUtil.setUser(res.getData());
//放行的Uri前缀
String uri = request.getRequestURI();
String contextPath = request.getContextPath();
String target = uri.replaceFirst(contextPath, "");
if (this.contain(this.getIgnoreUrlList(), target)) {
return true;
}
// 不需要登录
//不需要登录
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
// 检查是否包含 token
String xRequestToken = request.getParameter(RequestHeaderConst.TOKEN);
String xHeaderToken = request.getHeader(RequestHeaderConst.TOKEN);
String xAccessToken = StringUtils.isNotBlank(xRequestToken) ? xRequestToken : xHeaderToken;
// 包含token 则获取用户信息 并保存
if (StringUtils.isNotBlank(xAccessToken)) {
RequestUser requestUser = userFunction().apply(xAccessToken);
if (requestUser != null) {
SmartRequestUtil.setRequestUser(requestUser);
}
// 有token 无需登录
if (null != noNeedLogin) {
return true;
}
}
// 无token 无需登录
if (null != noNeedLogin) {
return true;
}
this.outputResult(response, res);
return false;
if (StringUtils.isBlank(xAccessToken)) {
this.outputResult(response, ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID));
return false;
}
return true;
}
/**
* 判断 sa-token 未登录场景值
* 自己根据业务在下面 switch 添加分支判断
* NotLoginException.NOT_TOKEN 无token
* NotLoginException.INVALID_TOKEN token无效
* NotLoginException.TOKEN_TIMEOUT token过期
* NotLoginException.NO_PREFIX token缺少前缀
* NotLoginException.KICK_OUT 已被踢下线
* NotLoginException.TOKEN_FREEZE 已被冻结
* <p>
* ps :之所以没有在全局异常里处理 是因为后续还有操作
*/
public ResponseDTO<RequestUser> checkTokenAndGetUser(HttpServletRequest request) {
/**
* 处理【非生产环境】的测试 token ,便于开发调试
* 如不需要 可以删除此段判断代码
*/
if (SystemEnvEnum.PROD != systemEnv.getCurrentEnv()) {
String tokenValue = StpUtil.getTokenValue();
if (NumberUtils.isDigits(tokenValue)) {
RequestUser user = this.getDevUser(NumberUtils.createLong(tokenValue));
this.handleRequestIpAndAgent(user, request);
// sa token 登录身份临时切换
StpUtil.switchTo(TokenService.generateLoginId(user.getUserId(), user.getUserType()));
return ResponseDTO.ok(user);
public Boolean contain(List<String> ignores, String uri) {
if (CollectionUtils.isEmpty(ignores)) {
return false;
}
for (String ignoreUrl : ignores) {
if (uri.startsWith(ignoreUrl)) {
return true;
}
}
try {
/**
* sa-token 会从当前请求 header or body 中获取token
* 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
*/
StpUtil.checkLogin();
} catch (NotLoginException e) {
switch (e.getType()) {
case NotLoginException.BE_REPLACED:
// token 已被顶下线
return ResponseDTO.error(UserErrorCode.LOGIN_FROM_OTHER);
// case NotLoginException.TOKEN_FREEZE:
// case NotLoginException.KICK_OUT:
default:
return ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID);
}
}
// 校验token的用户类型
UserTypeEnum systemUserTypeEnum = this.getUserType();
RequestUser user = this.buildCurrentUser(request);
if (null == user || systemUserTypeEnum != user.getUserType()) {
return ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID);
}
return ResponseDTO.ok(user);
return false;
}
/**
@@ -175,40 +136,13 @@ public abstract class AbstractInterceptor implements HandlerInterceptor {
*/
private void outputResult(HttpServletResponse response, ResponseDTO responseDTO) throws IOException {
String msg = JSONObject.toJSONString(responseDTO);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(msg);
response.flushBuffer();
}
/**
* build 当前请求用户
*
* @param request
* @return
*/
public RequestUser buildCurrentUser(HttpServletRequest request) {
// 获取额外数据
SaSession session = StpUtil.getSession();
UserTypeEnum userTypeEnum = (UserTypeEnum) session.get(TokenService.EXTRA_KEY_USER_TYPE);
String userName = session.getString(TokenService.EXTRA_KEY_USER_NAME);
// 当前请求对象
RequestUser user = new RequestUser();
user.setUserId(TokenService.getUserId((String) StpUtil.getLoginId()));
user.setUserName(userName);
user.setUserType(userTypeEnum);
this.handleRequestIpAndAgent(user, request);
return user;
}
/**
* 设置 当前请求ip agent
*
* @param requestUser
* @param request
*/
private void handleRequestIpAndAgent(RequestUser requestUser, HttpServletRequest request) {
requestUser.setUserAgent(ServletUtil.getHeaderIgnoreCase(request, RequestHeaderConst.USER_AGENT));
requestUser.setIp(ServletUtil.getClientIP(request));
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
SmartRequestUtil.remove();
}
}

View File

@@ -0,0 +1,93 @@
package net.lab1024.sa.common.common.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Spring Security
*
* @Author 1024创新实验室-主任: 卓大
* @Date 2021/8/3 17:50
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
public abstract class AbstractSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CorsFilter corsFilter;
@Autowired
private List<String> noNeedLoginUrlList;
@Autowired
private List<String> ignoreUrlList;
/**
* Token获取用户信息
*
* @return
*/
protected abstract BiFunction<String, HttpServletRequest, UserDetails> userFunction();
/**
* 需要认证的url集合
*
* @return
*/
protected abstract String[] getAuthenticatedUrlPatterns();
/**
* 不需要登录的url集合
*
* @return
*/
protected String[] getNoNeedLoginUrl() {
return noNeedLoginUrlList.toArray(new String[noNeedLoginUrlList.size()]);
}
/**
* 忽略的url集合
*
* @return
*/
protected String[] getIgnoreUrlList() {
return ignoreUrlList.toArray(new String[ignoreUrlList.size()]);
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF禁用因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(new SecurityAuthenticationFailHandler()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
//忽略的url
.antMatchers(this.getIgnoreUrlList()).permitAll()
// 不需要登陆的url
.antMatchers(this.getNoNeedLoginUrl()).permitAll()
//需要校验权限的url
.antMatchers(getAuthenticatedUrlPatterns()).authenticated();
// token filter 进行校验
httpSecurity.addFilterBefore(new SecurityTokenFilter(this.userFunction()), UsernamePasswordAuthenticationFilter.class);
httpSecurity.addFilterBefore(corsFilter, SecurityTokenFilter.class);
// 禁用spring security 使用 X-Frame-Options防止网页被Frame
httpSecurity.headers().frameOptions().disable();
}
}

View File

@@ -0,0 +1,43 @@
package net.lab1024.sa.common.common.security;
import com.alibaba.fastjson.JSONObject;
import net.lab1024.sa.common.common.code.ErrorCode;
import net.lab1024.sa.common.common.code.UserErrorCode;
import net.lab1024.sa.common.common.domain.ResponseDTO;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 登录认证失败处理
*
* @Author 1024创新实验室: 罗伊
* @Date 2022-08-26 20:21:10
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
public class SecurityAuthenticationFailHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
this.outputResult(response, UserErrorCode.LOGIN_STATE_INVALID);
}
/**
* 输出
*
* @param response
* @param errorCode
* @throws IOException
*/
private void outputResult(HttpServletResponse response, ErrorCode errorCode) throws IOException {
String msg = JSONObject.toJSONString(ResponseDTO.error(errorCode));
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(msg);
response.flushBuffer();
}
}

View File

@@ -0,0 +1,66 @@
package net.lab1024.sa.common.common.security;
import net.lab1024.sa.common.common.annoation.SaAuth;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.prepost.PreInvocationAttribute;
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource;
import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
/**
* 此类用于默认给所有接口添加权限 @saAuth.checkPermission('%s')
* %s 为类名.方法名
* 和使用@PreAuthorize("@saAuth.checkPermission('%s')") 效果一致
* 避免所有接口都添加一遍 减轻工作量
*
* @Author 1024创新实验室: 罗伊
* @Date 2021-08-30 23:08
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
public class SecurityMethodSource extends PrePostAnnotationSecurityMetadataSource {
private static String EXPRESSION_FORMAT = "@%s.checkPermission('%s')";
private final PrePostInvocationAttributeFactory attributeFactory;
private String beanName;
public SecurityMethodSource(PrePostInvocationAttributeFactory attributeFactory, String beanName) {
super(attributeFactory);
this.attributeFactory = attributeFactory;
this.beanName = beanName;
}
@Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
//如果不存在SaAuth采用security认证模式
SaAuth saAuth = method.getAnnotation(SaAuth.class);
if (saAuth == null) {
return super.getAttributes(method, targetClass);
}
//存在添加以URL为权限字符串的校验模式
ArrayList<ConfigAttribute> configAttributes = new ArrayList(1);
String classFullName = targetClass.getName();
String methodName = method.getName();
String[] classNameArray = StringUtils.split(classFullName, "\\.");
String controllerName = classNameArray[classNameArray.length - 1];
String privilegeName = controllerName + "." + methodName;
String preAuthorizeAttribute = String.format(EXPRESSION_FORMAT, beanName, privilegeName);
PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(null, null, preAuthorizeAttribute);
if (pre != null) {
configAttributes.add(pre);
}
return configAttributes;
}
}

View File

@@ -0,0 +1,74 @@
package net.lab1024.sa.common.common.security;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Arrays;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 校验权限
*
* @Author 1024创新实验室: 罗伊
* @Date 2022/5/12 21:50
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
public abstract class SecurityPermissionCheckService {
/**
* 校验是否有权限
*
* @param permission
* @return
*/
public boolean checkPermission(String permission) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return false;
}
return checkPermission(authentication, permission);
}
/**
* 校验是否有权限
*
* @param authentication
* @param permission
* @return
*/
public abstract boolean checkPermission(Authentication authentication, String permission);
/**
* 判断
*
* @param userDetails
* @param permissionStr
* @return
*/
protected boolean permissionJudge(UserDetails userDetails, String permissionStr) {
if (CollectionUtils.isEmpty(userDetails.getAuthorities())) {
return false;
}
if (StringUtils.isBlank(permissionStr)) {
return false;
}
String[] permissionArray = permissionStr.split(",");
for (String permission : permissionArray) {
if(userDetails.getAuthorities().contains(new SimpleGrantedAuthority(permission))){
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
package net.lab1024.sa.common.common.security;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.constant.RequestHeaderConst;
import net.lab1024.sa.common.common.domain.RequestUser;
import net.lab1024.sa.common.common.util.SmartRequestUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* 注意此处不能 加入@Component否则对应ignoreUrl的相关请求 将会进入此Filter并会覆盖CorsFilter
*
* @Author 1024创新实验室: 罗伊
* @Date 2022/5/12 21:50
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Slf4j
public class SecurityTokenFilter extends OncePerRequestFilter {
private BiFunction<String,HttpServletRequest, UserDetails> userFunction;
public SecurityTokenFilter(BiFunction<String,HttpServletRequest, UserDetails> userFunction) {
this.userFunction = userFunction;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
//需要做token校验, 消息头的token优先于请求query参数的token
String xHeaderToken = request.getHeader(RequestHeaderConst.TOKEN);
String xRequestToken = request.getParameter(RequestHeaderConst.TOKEN);
String xAccessToken = null != xHeaderToken ? xHeaderToken : xRequestToken;
if (StringUtils.isBlank(xAccessToken)) {
chain.doFilter(request, response);
return;
}
//清理spring security
SecurityContextHolder.clearContext();
UserDetails loginUserDetail = userFunction.apply(xAccessToken,request);
if (null != loginUserDetail) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUserDetail, null, loginUserDetail.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
SmartRequestUtil.setRequestUser((RequestUser) loginUserDetail);
}
// 若未给予spring security上下文用户授权 则会授权失败 进入AuthenticationEntryPointImpl
chain.doFilter(request, response);
}
}

View File

@@ -1,7 +1,6 @@
package net.lab1024.sa.common.common.util;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaStorage;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.domain.RequestUser;
/**
@@ -13,28 +12,26 @@ import net.lab1024.sa.common.common.domain.RequestUser;
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Slf4j
public class SmartRequestUtil {
private static final String STORAGE_KEY = "user";
private static final ThreadLocal<RequestUser> requestThreadLocal = new ThreadLocal<>();
public static void setUser(RequestUser user) {
SaStorage storage = SaHolder.getStorage();
storage.set(STORAGE_KEY, user);
public static void setRequestUser(RequestUser requestUser) {
requestThreadLocal.set(requestUser);
}
/**
* 获取 当前 token 请求用户
*
* @return
*/
public static RequestUser getUser() {
SaStorage storage = SaHolder.getStorage();
return storage.getModel(STORAGE_KEY, RequestUser.class);
public static RequestUser getRequestUser() {
return requestThreadLocal.get();
}
public static Long getUserId() {
RequestUser user = getUser();
return null != user ? user.getUserId() : null;
public static Long getRequestUserId() {
RequestUser requestUser = getRequestUser();
return null == requestUser ? null : requestUser.getUserId();
}
public static void remove() {
requestThreadLocal.remove();
}

View File

@@ -153,7 +153,7 @@ public class DataSourceConfig {
*
* @return
*/
@Conditional(SystemEnvConfig.class)
@Conditional(SystemEnvironmentConfig.class)
@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> servletRegistrationBean = new ServletRegistrationBean<>();

View File

@@ -1,19 +1,16 @@
package net.lab1024.sa.common.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import com.google.common.collect.Sets;
import net.lab1024.sa.common.common.interceptor.AbstractInterceptor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* web相关配置
@@ -28,28 +25,16 @@ import java.util.Set;
public class MvcConfig implements WebMvcConfigurer {
@Autowired(required = false)
private List<AbstractInterceptor> interceptorList;
@Autowired(required = false)
private List<SaInterceptor> saInterceptorList;
private List<HandlerInterceptor> interceptorList;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 先注册 登录拦截器
Set<String> ignoreUrlSet = Sets.newHashSet();
if (CollectionUtils.isNotEmpty(interceptorList)) {
interceptorList.forEach(e -> {
ignoreUrlSet.addAll(e.getIgnoreUrlList());
registry.addInterceptor(e).addPathPatterns(e.pathPatterns()).excludePathPatterns(e.getIgnoreUrlList());
});
}
// 后注册 sa-token 权限拦截器 不需要可以删除
if (CollectionUtils.isNotEmpty(saInterceptorList)) {
saInterceptorList.forEach(i -> {
registry.addInterceptor(i).addPathPatterns("/**").excludePathPatterns(new ArrayList<>(ignoreUrlSet));
});
public void addInterceptors (InterceptorRegistry registry) {
if (CollectionUtils.isEmpty(interceptorList)) {
return;
}
interceptorList.forEach(e->{
registry.addInterceptor(e).addPathPatterns("/**");
});
}
@Override

View File

@@ -31,7 +31,7 @@ public class RepeatSubmitConfig {
* @return
*/
private String ticket(String servletPath) {
Long userId = SmartRequestUtil.getUserId();
Long userId = SmartRequestUtil.getRequestUserId();
if (null == userId) {
return StringConst.EMPTY;
}

View File

@@ -57,7 +57,7 @@ import java.util.Map;
@Slf4j
@EnableSwagger2
@Configuration
@Conditional(SystemEnvConfig.class)
@Conditional(SystemEnvironmentConfig.class)
public class SwaggerConfig implements EnvironmentAware, BeanDefinitionRegistryPostProcessor {
/**
@@ -198,7 +198,7 @@ public class SwaggerConfig implements EnvironmentAware, BeanDefinitionRegistryPo
Parameter token = new ParameterBuilder().name(RequestHeaderConst.TOKEN)
.description("token")
.modelRef(new ModelRef("string"))
.parameterType("header").defaultValue("0")
.parameterType("header").defaultValue("1")
.required(false)
.build();
return Lists.newArrayList(token);

View File

@@ -1,7 +1,7 @@
package net.lab1024.sa.common.config;
import net.lab1024.sa.common.common.domain.SystemEnv;
import net.lab1024.sa.common.common.enumeration.SystemEnvEnum;
import net.lab1024.sa.common.common.domain.SystemEnvironment;
import net.lab1024.sa.common.common.enumeration.SystemEnvironmentEnum;
import net.lab1024.sa.common.common.util.SmartEnumUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@@ -21,7 +21,7 @@ import org.springframework.core.type.AnnotatedTypeMetadata;
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Configuration
public class SystemEnvConfig implements Condition {
public class SystemEnvironmentConfig implements Condition {
@Value("${spring.profiles.active}")
private String systemEnvironment;
@@ -32,18 +32,18 @@ public class SystemEnvConfig implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
String property = conditionContext.getEnvironment().getProperty("spring.profiles.active");
return StringUtils.isNotBlank(property) && !SystemEnvEnum.PROD.equalsValue(property);
return StringUtils.isNotBlank(property) && !SystemEnvironmentEnum.PROD.equalsValue(property);
}
@Bean
public SystemEnv initEnvironment() {
SystemEnvEnum currentEnv = SmartEnumUtil.getEnumByValue(systemEnvironment, SystemEnvEnum.class);
if (currentEnv == null) {
public SystemEnvironment initEnvironment() {
SystemEnvironmentEnum currentEnvironment = SmartEnumUtil.getEnumByValue(systemEnvironment, SystemEnvironmentEnum.class);
if (currentEnvironment == null) {
throw new ExceptionInInitializerError("无法获取当前环境!请在 application.yaml 配置参数spring.profiles.active");
}
if (StringUtils.isBlank(projectName)) {
throw new ExceptionInInitializerError("无法获取当前项目名称!请在 application.yaml 配置参数project.name");
}
return new SystemEnv(currentEnv == SystemEnvEnum.PROD, projectName, currentEnv);
return new SystemEnvironment(currentEnvironment == SystemEnvironmentEnum.PROD, projectName, currentEnvironment);
}
}

View File

@@ -1,11 +1,12 @@
package net.lab1024.sa.common.config;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.annoation.NoNeedLogin;
import net.lab1024.sa.common.common.annoation.SaAuth;
import net.lab1024.sa.common.common.domain.RequestUrlVO;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,18 +23,21 @@ import java.util.Set;
import java.util.stream.Collectors;
/**
* description
* url配置
*
* @author Turbolisten
* @date 2023/7/13 17:42
* @Author 1024创新实验室: 罗伊
* @Date 2022-05-30 21:22:12
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Slf4j
@Configuration
@Slf4j
public class UrlConfig {
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
/**
* 获取每个方法的请求路径
*
@@ -42,10 +46,11 @@ public class UrlConfig {
@Bean
public Map<Method, Set<String>> methodUrlMap() {
Map<Method, Set<String>> methodUrlMap = Maps.newHashMap();
// 获取url与类和方法的对应信息
//获取url与类和方法的对应信息
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
RequestMappingInfo requestMappingInfo = entry.getKey();
Set<String> urls = requestMappingInfo.getPatternsCondition().getPatterns();
if (CollectionUtils.isEmpty(urls)) {
continue;
@@ -67,20 +72,14 @@ public class UrlConfig {
List<RequestUrlVO> authUrlList = Lists.newArrayList();
for (Map.Entry<Method, Set<String>> entry : methodUrlMap.entrySet()) {
Method method = entry.getKey();
// 忽略权限
SaIgnore ignore = method.getAnnotation(SaIgnore.class);
if (null != ignore) {
SaAuth saAuth = method.getAnnotation(SaAuth.class);
if (null == saAuth) {
continue;
}
NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class);
if (null != noNeedLogin) {
continue;
}
Set<String> urlSet = entry.getValue();
List<RequestUrlVO> requestUrlList = this.buildRequestUrl(method, urlSet);
List<RequestUrlVO> requestUrlList = this.buildRequestUrl(method, entry.getValue());
authUrlList.addAll(requestUrlList);
}
log.info("需要权限校验的URL{}", authUrlList.stream().map(RequestUrlVO::getUrl).collect(Collectors.toList()));
log.info("需要权限校验的URL{}", authUrlList.stream().map(e -> e.getUrl()).collect(Collectors.toList()));
return authUrlList;
}
@@ -89,22 +88,29 @@ public class UrlConfig {
if (CollectionUtils.isEmpty(urlSet)) {
return requestUrlList;
}
// swagger api 说明
//url对应的方法名称
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
List<String> list = StrUtil.split(className, ".");
String controllerName = list.get(list.size() - 1);
String name = controllerName + "." + methodName;
//swagger 说明信息
String methodComment = null;
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
if (apiOperation != null) {
methodComment = apiOperation.value();
}
for (String url : urlSet) {
RequestUrlVO requestUrlVO = new RequestUrlVO();
requestUrlVO.setUrl(url);
requestUrlVO.setName(name);
requestUrlVO.setComment(methodComment);
requestUrlList.add(requestUrlVO);
}
return requestUrlList;
}
/**
* 获取无需登录可以匿名访问的url信息
*
@@ -124,4 +130,21 @@ public class UrlConfig {
log.info("不需要登录的URL{}", noNeedLoginUrlList);
return noNeedLoginUrlList;
}
}
/**
* 获取忽略的url信息
*
* @return
*/
@Bean
public List<String> ignoreUrlList() {
List<String> ignoreUrlList = Lists.newArrayList();
ignoreUrlList.add("/swagger-ui.html");
ignoreUrlList.add("/swagger-resources/**");
ignoreUrlList.add("/webjars/**");
ignoreUrlList.add("/druid/**");
ignoreUrlList.add("/*/api-docs");
return ignoreUrlList;
}
}

View File

@@ -1,16 +1,15 @@
package net.lab1024.sa.common.handler;
import cn.dev33.satoken.exception.NotPermissionException;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.code.SystemErrorCode;
import net.lab1024.sa.common.common.code.UserErrorCode;
import net.lab1024.sa.common.common.domain.ResponseDTO;
import net.lab1024.sa.common.common.domain.SystemEnv;
import net.lab1024.sa.common.common.enumeration.SystemEnvEnum;
import net.lab1024.sa.common.common.domain.SystemEnvironment;
import net.lab1024.sa.common.common.exception.BusinessException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -38,7 +37,7 @@ import java.util.stream.Collectors;
public class GlobalExceptionHandler {
@Autowired
private SystemEnv systemEnv;
private SystemEnvironment systemEnvironment;
/**
* json 格式错误 缺少请求体
@@ -46,7 +45,7 @@ public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler({HttpMessageNotReadableException.class})
public ResponseDTO<?> jsonFormatExceptionHandler(Exception e) {
if (!systemEnv.isProd()) {
if (!systemEnvironment.isProd()) {
log.error("全局JSON格式错误异常,URL:{}", getCurrentRequestUrl(), e);
}
return ResponseDTO.error(UserErrorCode.PARAM_ERROR, "参数JSON格式错误");
@@ -58,7 +57,7 @@ public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler({TypeMismatchException.class, BindException.class})
public ResponseDTO<?> paramExceptionHandler(Exception e) {
if (!systemEnv.isProd()) {
if (!systemEnvironment.isProd()) {
log.error("全局参数异常,URL:{}", getCurrentRequestUrl(), e);
}
@@ -78,34 +77,27 @@ public class GlobalExceptionHandler {
return ResponseDTO.error(UserErrorCode.PARAM_ERROR);
}
/**
* 权限异常
*/
@ResponseBody
@ExceptionHandler({AccessDeniedException.class})
public ResponseDTO<?> permissionExceptionHandler(AccessDeniedException e) {
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
/**
* 业务异常
*/
@ResponseBody
@ExceptionHandler(BusinessException.class)
public ResponseDTO<?> businessExceptionHandler(BusinessException e) {
if (!systemEnv.isProd()) {
if (!systemEnvironment.isProd()) {
log.error("全局业务异常,URL:{}", getCurrentRequestUrl(), e);
}
return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, e.getMessage());
}
/**
* sa-token 权限异常处理
*
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(NotPermissionException.class)
public ResponseDTO<String> permissionException(NotPermissionException e) {
// 开发环境 方便调试
if (SystemEnvEnum.PROD != systemEnv.getCurrentEnv()) {
return ResponseDTO.error(UserErrorCode.NO_PERMISSION, e.getMessage());
}
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
}
/**
* 其他全部异常
*
@@ -116,7 +108,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(Throwable.class)
public ResponseDTO<?> errorHandler(Throwable e) {
log.error("捕获全局异常,URL:{}", getCurrentRequestUrl(), e);
return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, systemEnv.isProd() ? null : e.toString());
return ResponseDTO.error(SystemErrorCode.SYSTEM_ERROR, systemEnvironment.isProd() ? null : e.toString());
}
/**

View File

@@ -2,9 +2,10 @@ package net.lab1024.sa.common.module.support.captcha;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.code.UserErrorCode;
import net.lab1024.sa.common.common.constant.StringConst;
import net.lab1024.sa.common.common.domain.ResponseDTO;
import net.lab1024.sa.common.common.domain.SystemEnv;
import net.lab1024.sa.common.common.domain.SystemEnvironment;
import net.lab1024.sa.common.common.exception.BusinessException;
import net.lab1024.sa.common.constant.RedisKeyConst;
import net.lab1024.sa.common.module.support.captcha.domain.CaptchaForm;
@@ -42,7 +43,7 @@ public class CaptchaService {
@Autowired
private DefaultKaptcha defaultKaptcha;
@Autowired
private SystemEnv systemEnvironment;
private SystemEnvironment systemEnvironment;
@Autowired
private RedisService redisService;

View File

@@ -157,7 +157,7 @@ public class DataTracerService {
* 保存数据变动记录
*/
public void addTrace(DataTracerForm tracerForm) {
RequestUser requestUser = SmartRequestUtil.getUser();
RequestUser requestUser = SmartRequestUtil.getRequestUser();
this.addTrace(tracerForm, requestUser);
}
@@ -182,7 +182,7 @@ public class DataTracerService {
* 批量保存数据变动记录
*/
public void addTraceList(List<DataTracerForm> tracerFormList) {
RequestUser requestUser = SmartRequestUtil.getUser();
RequestUser requestUser = SmartRequestUtil.getRequestUser();
this.addTraceList(tracerFormList, requestUser);
}

View File

@@ -13,6 +13,7 @@ import net.lab1024.sa.common.module.support.feedback.domain.FeedbackAddForm;
import net.lab1024.sa.common.module.support.feedback.domain.FeedbackQueryForm;
import net.lab1024.sa.common.module.support.feedback.domain.FeedbackVO;
import net.lab1024.sa.common.module.support.feedback.service.FeedbackService;
import net.lab1024.sa.common.module.support.repeatsubmit.annoation.RepeatSubmit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -46,7 +47,7 @@ public class FeedbackController extends SupportBaseController {
@ApiOperation("意见反馈-新增 @author 开云")
@PostMapping("/feedback/add")
public ResponseDTO<String> add(@RequestBody @Valid FeedbackAddForm addForm) {
RequestUser employee = SmartRequestUtil.getUser();
RequestUser employee = SmartRequestUtil.getRequestUser();
return feedbackService.add(addForm, employee);
}
}

View File

@@ -5,13 +5,17 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import net.lab1024.sa.common.common.constant.RequestHeaderConst;
import net.lab1024.sa.common.common.controller.SupportBaseController;
import net.lab1024.sa.common.common.domain.PageResult;
import net.lab1024.sa.common.common.domain.RequestUser;
import net.lab1024.sa.common.common.domain.ResponseDTO;
import net.lab1024.sa.common.common.util.SmartRequestUtil;
import net.lab1024.sa.common.constant.SwaggerTagConst;
import net.lab1024.sa.common.module.support.file.constant.FileFolderTypeEnum;
import net.lab1024.sa.common.module.support.file.domain.form.FileQueryForm;
import net.lab1024.sa.common.module.support.file.domain.form.FileUrlUploadForm;
import net.lab1024.sa.common.module.support.file.domain.vo.FileUploadVO;
import net.lab1024.sa.common.module.support.file.domain.vo.FileVO;
import net.lab1024.sa.common.module.support.repeatsubmit.annoation.RepeatSubmit;
import net.lab1024.sa.common.module.support.file.constant.FileFolderTypeEnum;
import net.lab1024.sa.common.module.support.file.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@@ -42,14 +46,14 @@ public class FileController extends SupportBaseController {
@PostMapping("/file/upload")
public ResponseDTO<FileUploadVO> upload(@RequestParam MultipartFile file,
@RequestParam Integer folder) {
RequestUser requestUser = SmartRequestUtil.getUser();
RequestUser requestUser = SmartRequestUtil.getRequestUser();
return fileService.fileUpload(file, folder, requestUser);
}
@ApiOperation(value = "文件上传通过url上传 @author 胡克", notes = FileFolderTypeEnum.INFO)
@PostMapping("/file/upload/url")
public ResponseDTO<FileUploadVO> uploadByUrl(@RequestBody @Valid FileUrlUploadForm uploadForm) {
RequestUser requestUser = SmartRequestUtil.getUser();
RequestUser requestUser = SmartRequestUtil.getRequestUser();
return fileService.fileUpload(uploadForm,requestUser);
}

View File

@@ -56,7 +56,7 @@ public class HelpDocController extends SupportBaseController {
@RepeatSubmit
public ResponseDTO<HelpDocDetailVO> view(@PathVariable Long helpDocId, HttpServletRequest request) {
return helpDocUserService.view(
SmartRequestUtil.getUser(),
SmartRequestUtil.getRequestUser(),
helpDocId);
}

View File

@@ -179,7 +179,7 @@ public abstract class OperateLogAspect {
return;
}
//设置用户信息
RequestUser user = SmartRequestUtil.getUser();
RequestUser user = SmartRequestUtil.getRequestUser();
if (user == null) {
return;
}

View File

@@ -1,8 +1,8 @@
package net.lab1024.sa.common.module.support.redis;
import com.alibaba.fastjson.JSON;
import net.lab1024.sa.common.common.domain.SystemEnv;
import net.lab1024.sa.common.common.enumeration.SystemEnvEnum;
import net.lab1024.sa.common.common.domain.SystemEnvironment;
import net.lab1024.sa.common.common.enumeration.SystemEnvironmentEnum;
import net.lab1024.sa.common.common.util.SmartStringUtil;
import net.lab1024.sa.common.constant.RedisKeyConst;
import org.slf4j.Logger;
@@ -52,7 +52,7 @@ public class RedisService {
private SetOperations<String, Object> redisSetOperations;
@Autowired
private SystemEnv systemEnvironment;
private SystemEnvironment systemEnvironment;
/**
@@ -62,7 +62,7 @@ public class RedisService {
* @return
*/
public String generateRedisKey(String prefix, String key) {
SystemEnvEnum currentEnvironment = systemEnvironment.getCurrentEnv();
SystemEnvironmentEnum currentEnvironment = systemEnvironment.getCurrentEnvironment();
return systemEnvironment.getProjectName() + RedisKeyConst.SEPARATOR + currentEnvironment.getValue() + RedisKeyConst.SEPARATOR + prefix + key;
}

View File

@@ -33,19 +33,19 @@ public class TableColumnController extends SupportBaseController {
@PostMapping("/tableColumn/update")
@RepeatSubmit
public ResponseDTO<String> updateTableColumn(@RequestBody @Valid TableColumnUpdateForm updateForm) {
return tableColumnService.updateTableColumns(SmartRequestUtil.getUser(), updateForm);
return tableColumnService.updateTableColumns(SmartRequestUtil.getRequestUser(), updateForm);
}
@ApiOperation("恢复默认(删除) @author 卓大")
@GetMapping("/tableColumn/delete/{tableId}")
@RepeatSubmit
public ResponseDTO<String> deleteTableColumn(@PathVariable Integer tableId) {
return tableColumnService.deleteTableColumn(SmartRequestUtil.getUser(), tableId);
return tableColumnService.deleteTableColumn(SmartRequestUtil.getRequestUser(), tableId);
}
@ApiOperation("查询表格列 @author 卓大")
@GetMapping("/tableColumn/getColumns/{tableId}")
public ResponseDTO<String> getColumns(@PathVariable Integer tableId) {
return ResponseDTO.ok(tableColumnService.getTableColumns(SmartRequestUtil.getUser(), tableId));
return ResponseDTO.ok(tableColumnService.getTableColumns(SmartRequestUtil.getRequestUser(), tableId));
}
}

View File

@@ -1,7 +1,5 @@
package net.lab1024.sa.common.module.support.token;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.lab1024.sa.common.common.enumeration.BaseEnum;
/**
@@ -13,8 +11,6 @@ import net.lab1024.sa.common.common.enumeration.BaseEnum;
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Getter
@AllArgsConstructor
public enum LoginDeviceEnum implements BaseEnum {
PC(1, "电脑端"),
@@ -25,9 +21,23 @@ public enum LoginDeviceEnum implements BaseEnum {
H5(4, "H5"),
WX_MP(5, "微信小程序");
WEIXIN_MP(5, "微信小程序");
private final Integer value;
LoginDeviceEnum(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
private final String desc;
private Integer value;
private String desc;
@Override
public Integer getValue() {
return value;
}
@Override
public String getDesc() {
return desc;
}
}

View File

@@ -1,85 +1,220 @@
package net.lab1024.sa.common.module.support.token;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import net.lab1024.sa.common.common.constant.StringConst;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import net.lab1024.sa.common.common.enumeration.UserTypeEnum;
import net.lab1024.sa.common.constant.RedisKeyConst;
import net.lab1024.sa.common.module.support.redis.RedisService;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Date;
import java.util.Map;
/**
* 用户token 相关服务
* 用户token相关服务
*
* @author listen
* @date 2023-07-12 22:48:35
* @Author 1024创新实验室-主任: 卓大
* @Date 2021-11-29 19:48:35
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net
*/
@Component
@Slf4j
public class TokenService {
private static final long HOUR_TIME_MILLI = 60 * 60 * 1000;
public static final String EXTRA_KEY_USER_NAME = "userName";
@Value("${token.key}")
private String tokenKey;
public static final String EXTRA_KEY_USER_TYPE = "userType";
@Value("${token.expire-day}")
private Integer tokenExpire;
@Autowired
private RedisService redisService;
/**
* 生成Token
* 生成Token并存入redis
*
* @param userId
* @param userName
* @param userTypeEnum
* @param loginDeviceEnum
* @param superPasswordFlag 特殊万能密码标识
* @return
*/
public String generateToken(Long userId,
String userName,
UserTypeEnum userTypeEnum,
LoginDeviceEnum loginDeviceEnum) {
public String generateToken(Long userId, String userName, UserTypeEnum userTypeEnum, LoginDeviceEnum loginDeviceEnum, Boolean superPasswordFlag) {
long nowTimeMilli = System.currentTimeMillis();
Claims jwtClaims = Jwts.claims();
jwtClaims.put(JwtConst.CLAIM_ID_KEY, userId);
jwtClaims.put(JwtConst.CLAIM_NAME_KEY, userName);
jwtClaims.put(JwtConst.CLAIM_USER_TYPE_KEY, userTypeEnum.getValue());
jwtClaims.put(JwtConst.CLAIM_DEVICE_KEY, loginDeviceEnum.getValue());
jwtClaims.put(JwtConst.CLAIM_SUPER_PASSWORD_FLAG, superPasswordFlag);
JwtBuilder jwtBuilder = Jwts.builder()
.setClaims(jwtClaims)
.setIssuedAt(new Date(nowTimeMilli))
.signWith(SignatureAlgorithm.HS512, tokenKey);
/**
* 设置登录模式参数
* 具体参数 {@link SaLoginModel } 属性
* 已经写的挺清楚的了
*/
SaLoginModel loginModel = new SaLoginModel();
// 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型
loginModel.setDevice(String.valueOf(loginDeviceEnum.getDesc()));
// 如果是万能密码则不需要记录到redis中;万能密码最多半个小时有效期
if (superPasswordFlag) {
jwtBuilder.setExpiration(new Date(nowTimeMilli + (HOUR_TIME_MILLI / 2)));
return jwtBuilder.compact();
}
// 登录
String loginId = generateLoginId(userId, userTypeEnum);
StpUtil.login(loginId, loginModel);
// 扩展参数 放入会话中 redis session
SaSession session = StpUtil.getSession();
session.set(EXTRA_KEY_USER_NAME, userName);
session.set(EXTRA_KEY_USER_TYPE, userTypeEnum);
return StpUtil.getTokenValue();
}
public static String generateLoginId(Long userId, UserTypeEnum userType) {
return userType.getValue() + StringConst.COLON + userId;
}
public static Long getUserId(String loginId) {
return Long.valueOf(loginId.substring(loginId.indexOf(StringConst.COLON) + 1));
}
public static Integer getUserType(String loginId) {
return Integer.valueOf(loginId.substring(0, loginId.indexOf(StringConst.COLON)));
jwtBuilder.setExpiration(new Date(nowTimeMilli + tokenExpire * 24 * HOUR_TIME_MILLI));
String token = jwtBuilder.compact();
String redisKey = this.generateTokenRedisKey(userId, userTypeEnum.getValue(), loginDeviceEnum.getValue());
redisService.set(redisKey, token, tokenExpire * 24 * 3600);
return token;
}
/**
* 退出登录 注销
* 生成登录信息: 含设备信息
*
* @param userId
* @param device
* @return
*/
public void removeToken() {
StpUtil.logout();
private String generateTokenRedisKey(Long userId, Integer userType, Integer device) {
String userKey = userType + "_" + userId + "_" + device;
return redisService.generateRedisKey(RedisKeyConst.Support.TOKEN, userKey);
}
public void removeToken(Long userId, UserTypeEnum userType) {
StpUtil.logout(generateLoginId(userId, userType));
/**
* 强制移除 此用户各端的登录信息
*
* @param token
*/
public void removeToken(String token) {
Map<String, Object> tokenData = this.decryptTokenData(token);
if (MapUtils.isEmpty(tokenData)) {
return;
}
//特殊账号
if (tokenData.get(JwtConst.CLAIM_SUPER_PASSWORD_FLAG) != null) {
try {
Boolean superPasswordFlag = Boolean.valueOf(tokenData.get(JwtConst.CLAIM_SUPER_PASSWORD_FLAG).toString());
if (superPasswordFlag) {
return;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return;
}
}
boolean isValid = this.checkRedisToken(tokenData, token);
if (!isValid) {
return;
}
Long userId = Long.valueOf(tokenData.get(JwtConst.CLAIM_ID_KEY).toString());
Integer userType = Integer.valueOf(tokenData.get(JwtConst.CLAIM_USER_TYPE_KEY).toString());
Integer device = Integer.valueOf(tokenData.get(JwtConst.CLAIM_DEVICE_KEY).toString());
String redisKey = this.generateTokenRedisKey(userId, userType, device);
redisService.delete(redisKey);
}
public void removeToken(List<Long> userIdList, UserTypeEnum userType) {
userIdList.forEach(id -> StpUtil.logout(generateLoginId(id, userType)));
/**
* 解析并校验token信息 获取 userId
*
* @param token
* @return
*/
public Long getUserIdAndValidateToken(String token) {
Map<String, Object> parseJwtData = this.decryptTokenData(token);
boolean isValid = this.checkRedisToken(parseJwtData, token);
if (!isValid) {
return null;
}
Long userId = Long.valueOf(parseJwtData.get(JwtConst.CLAIM_ID_KEY).toString());
return userId;
}
/**
* 解密和解析token
*
* @param token
* @return
*/
private Map<String, Object> decryptTokenData(String token) {
try {
return Jwts.parser()
.setSigningKey(tokenKey)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
}
return null;
}
/**
* 校验token是否有效
*
* @param token
* @return
*/
private boolean checkRedisToken(Map<String, Object> parseJwtData, String token) {
if (MapUtils.isEmpty(parseJwtData)) {
return false;
}
//特殊账号
if (parseJwtData.get(JwtConst.CLAIM_SUPER_PASSWORD_FLAG) != null) {
try {
Boolean superPasswordFlag = Boolean.valueOf(parseJwtData.get(JwtConst.CLAIM_SUPER_PASSWORD_FLAG).toString());
if (superPasswordFlag) {
return true;
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
Long userId = null;
Integer userType = null, device = null;
if (null != parseJwtData.get(JwtConst.CLAIM_ID_KEY)) {
userId = NumberUtils.toLong(parseJwtData.get(JwtConst.CLAIM_ID_KEY).toString(), -1);
userId = userId == -1 ? null : userId;
}
if (null != parseJwtData.get(JwtConst.CLAIM_USER_TYPE_KEY)) {
userType = NumberUtils.toInt(parseJwtData.get(JwtConst.CLAIM_USER_TYPE_KEY).toString(), -1);
userType = userType == -1 ? null : userType;
}
if (null != parseJwtData.get(JwtConst.CLAIM_DEVICE_KEY)) {
device = NumberUtils.toInt(parseJwtData.get(JwtConst.CLAIM_DEVICE_KEY).toString(), -1);
device = device == -1 ? null : device;
}
if (userId == null || userType == null || device == null) {
return false;
}
String redisKey = this.generateTokenRedisKey(userId, userType, device);
String redisToken = redisService.get(redisKey);
return token.equals(redisToken);
}
/**
* 批量移除用户所有设备的token
*/
public void batchRemoveRedisToken(Long userId, UserTypeEnum userTypeEnum) {
for (LoginDeviceEnum device : LoginDeviceEnum.values()) {
redisService.delete(this.generateTokenRedisKey(userId, userTypeEnum.getValue(), device.getValue()));
}
}
}

View File

@@ -3,7 +3,7 @@ spring:
datasource:
url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v2?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
password: Zhuoda1024lab
initial-size: 2
min-idle: 2
max-active: 10
@@ -27,17 +27,17 @@ spring:
# redis 连接池配置信息
redis:
database: 12
database: 1
host: 127.0.0.1
lettuce:
pool:
max-active: 50
max-active: 5
min-idle: 1
max-idle: 3
max-wait: 30000ms
port: 6379
timeout: 10s
password: 123456
timeout: 10000ms
password:
# 上传文件大小配置
servlet:
@@ -109,6 +109,11 @@ http:
write-timeout: 50000
keep-alive: 300000
# token相关配置
token:
key: sa-jwt-key
expire-day: 7
# 跨域配置
access-control-allow-origin: '*'
@@ -118,33 +123,4 @@ heart-beat:
# 热加载配置
reload:
interval-seconds: 300
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: x-access-token
# token 前缀 例如Bear
token-prefix:
# jwt秘钥
# jwt-secret-key: smart-admin
# token 有效期(单位:秒) 默认30天-1 代表永久有效
timeout: 432000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: false
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token(jwt模式下恒false)
is-share: false
# token 风格默认可取值uuid、simple-uuid、random-32、random-64、random-128、tik(jwt模式下无用)
token-style: simple-uuid
# 是否打开自动续签 如果此值为true框架会在每次直接或间接调用 getLoginId() 时进行一次过期检查与续签操作)
auto-renew: true
# 是否输出操作日志
is-log: true
# 日志等级trace、debug、info、warn、error、fatal
log-level: debug
# 启动时的字符画打印
is-print: false
# 是否从cookie读取token
is-read-cookie: false
interval-seconds: 300