refactor: 代码重构,移除 jjwt 的 JWT 库,使用 HuTool 工具实现 JWT 生成(默认jjwt库)、验证和解析。
This commit is contained in:
@@ -192,6 +192,7 @@ public class GlobalExceptionHandler {
|
||||
throw e;
|
||||
}
|
||||
log.error("unknown exception: {}", e.getMessage());
|
||||
e.printStackTrace();
|
||||
return Result.failed(e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ package com.youlai.system.config;
|
||||
import com.youlai.system.common.constant.SecurityConstants;
|
||||
import com.youlai.system.core.security.exception.MyAccessDeniedHandler;
|
||||
import com.youlai.system.core.security.exception.MyAuthenticationEntryPoint;
|
||||
import com.youlai.system.core.security.jwt.JwtTokenFilter;
|
||||
import com.youlai.system.filter.JwtTokenFilter;
|
||||
import com.youlai.system.filter.VerifyCodeFilter;
|
||||
import com.youlai.system.core.security.jwt.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -36,7 +35,6 @@ public class SecurityConfig {
|
||||
|
||||
private final MyAuthenticationEntryPoint authenticationEntryPoint;
|
||||
private final MyAccessDeniedHandler accessDeniedHandler;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
@@ -58,7 +56,7 @@ public class SecurityConfig {
|
||||
// 验证码校验过滤器
|
||||
http.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
// JWT 校验过滤器
|
||||
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
|
||||
http.addFilterBefore(new JwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.youlai.system.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.system.core.security.jwt.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import com.youlai.system.util.JwtUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -26,12 +25,9 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocketMessageBroker // 启用WebSocket消息代理功能和配置STOMP协议,实现实时双向通信和消息传递
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
/**
|
||||
* 注册一个端点,客户端通过这个端点进行连接
|
||||
*/
|
||||
@@ -83,8 +79,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
||||
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
try {
|
||||
// 移除 "Bearer " 前缀,从令牌中提取用户信息(username), 并设置到认证信息中
|
||||
String tokenWithoutPrefix = bearerToken.substring(7);
|
||||
String username = jwtTokenProvider.getUsername(tokenWithoutPrefix);
|
||||
String username = JwtUtils.parseToken(bearerToken).get("name").toString();
|
||||
|
||||
if (StrUtil.isNotBlank(username)) {
|
||||
accessor.setUser(() -> username);
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.youlai.system.common.result.PageResult;
|
||||
import com.youlai.system.common.result.Result;
|
||||
import com.youlai.system.common.util.ExcelUtils;
|
||||
import com.youlai.system.util.ExcelUtils;
|
||||
import com.youlai.system.plugin.dupsubmit.annotation.PreventDuplicateSubmit;
|
||||
import com.youlai.system.plugin.easyexcel.UserImportListener;
|
||||
import com.youlai.system.model.vo.UserImportVO;
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.mapstruct.Mappings;
|
||||
public interface UserConverter {
|
||||
|
||||
@Mappings({
|
||||
@Mapping(target = "genderLabel", expression = "java(com.youlai.system.common.base.IBaseEnum.getLabelByValue(bo.getGender(), com.youlai.system.common.enums.GenderEnum.class))")
|
||||
@Mapping(target = "genderLabel", expression = "java(com.youlai.system.common.base.IBaseEnum.getLabelByValue(bo.getGender(), com.youlai.system.enums.GenderEnum.class))")
|
||||
})
|
||||
UserPageVO toPageVo(UserBO bo);
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
|
||||
import com.youlai.system.core.mybatis.annotation.DataPermission;
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import com.youlai.system.common.enums.DataScopeEnum;
|
||||
import com.youlai.system.common.util.SecurityUtils;
|
||||
import com.youlai.system.enums.DataScopeEnum;
|
||||
import com.youlai.system.util.SecurityUtils;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.system.core.security.exception;
|
||||
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.common.util.ResponseUtils;
|
||||
import com.youlai.system.util.ResponseUtils;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.system.core.security.exception;
|
||||
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.common.util.ResponseUtils;
|
||||
import com.youlai.system.util.ResponseUtils;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
package com.youlai.system.core.security.jwt;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.youlai.system.common.constant.JwtClaimConstants;
|
||||
import com.youlai.system.core.security.model.SysUserDetails;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.io.DecodingException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT token 工具类
|
||||
* <p>
|
||||
* 用于生成/校验/解析 JWT Token
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2023/9/13
|
||||
*/
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
|
||||
/**
|
||||
* 签名密钥,用于签名 Access Token
|
||||
*/
|
||||
@Value("${jwt.secret-key:123456}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${jwt.expiration:7200}")
|
||||
private int expiration;
|
||||
|
||||
/**
|
||||
* Base64 编码后的签名密钥,用于校验/解析 Access Token
|
||||
*/
|
||||
private byte[] secretKeyBytes;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
* <p>
|
||||
* 对签名密钥进行 Base64 编码
|
||||
*/
|
||||
@PostConstruct
|
||||
protected void init() {
|
||||
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Token
|
||||
* <p>
|
||||
* 认证成功后的用户信息会被封装到 Authentication 对象中,然后通过 JwtTokenProvider#createToken(Authentication) 方法创建 Token 字符串
|
||||
*
|
||||
* @param authentication 用户认证信息
|
||||
* @return Token 字符串
|
||||
*/
|
||||
public String createToken(Authentication authentication) {
|
||||
Claims claims = Jwts.claims().setSubject(authentication.getName());
|
||||
|
||||
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
|
||||
claims.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID
|
||||
claims.put(JwtClaimConstants.USERNAME, claims.getSubject()); // 用户名
|
||||
claims.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID
|
||||
claims.put(JwtClaimConstants.DATA_SCOPE, userDetails.getDataScope()); // 数据权限范围
|
||||
|
||||
// claims 中添加角色信息
|
||||
Set<String> roles = userDetails.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toSet());
|
||||
claims.put(JwtClaimConstants.AUTHORITIES, roles);
|
||||
|
||||
Date now = new Date();
|
||||
Date expirationTime = new Date(now.getTime() + expiration * 1000L);
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expirationTime)
|
||||
.signWith(Keys.hmacShaKeyFor(getSecretKeyBytes()), SignatureAlgorithm.HS256).compact();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据给定的令牌解析出用户认证信息
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 用户认证信息
|
||||
*/
|
||||
public Authentication getAuthentication(String token) {
|
||||
Claims claims = this.getTokenClaims(token);
|
||||
|
||||
SysUserDetails userDetails = new SysUserDetails();
|
||||
userDetails.setUserId(Convert.toLong(claims.get(JwtClaimConstants.USER_ID))); // 用户ID
|
||||
userDetails.setUsername(Convert.toStr(claims.get(JwtClaimConstants.USERNAME))); // 用户名
|
||||
userDetails.setDeptId(Convert.toLong(claims.get(JwtClaimConstants.DEPT_ID))); // 部门ID
|
||||
userDetails.setDataScope(Convert.toInt(claims.get(JwtClaimConstants.DATA_SCOPE))); // 数据权限范围
|
||||
|
||||
// 角色集合
|
||||
Set<SimpleGrantedAuthority> authorities = ((ArrayList<String>) claims.get(JwtClaimConstants.AUTHORITIES))
|
||||
.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从请求头中获取Token
|
||||
*
|
||||
* @param req 请求对象
|
||||
* @return Token 字符串
|
||||
*/
|
||||
public String resolveToken(HttpServletRequest req) {
|
||||
String bearerToken = req.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验Token是否有效
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 是否有效
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
Jwts.parserBuilder().setSigningKey(getSecretKeyBytes()).build().parseClaimsJws(token);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token中的用户名
|
||||
*
|
||||
* @param token Token
|
||||
* @return
|
||||
*/
|
||||
public String getUsername(String token) {
|
||||
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token的Claims,claims中包含了用户的基本信息
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public Claims getTokenClaims(String token) {
|
||||
return Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build().parseClaimsJws(token).getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名密钥的字节数组
|
||||
*
|
||||
* @return 签名密钥的字节数组
|
||||
*/
|
||||
public byte[] getSecretKeyBytes() {
|
||||
if (secretKeyBytes == null) {
|
||||
try {
|
||||
secretKeyBytes = Decoders.BASE64.decode(secretKey);
|
||||
} catch (DecodingException e) {
|
||||
secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return secretKeyBytes;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.youlai.system.core.security.service;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.system.common.constant.CacheConstants;
|
||||
import com.youlai.system.common.util.SecurityUtils;
|
||||
import com.youlai.system.util.SecurityUtils;
|
||||
import com.youlai.system.model.bo.RolePermsBO;
|
||||
import com.youlai.system.service.SysRoleMenuService;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.enums;
|
||||
package com.youlai.system.enums;
|
||||
|
||||
/**
|
||||
* EasyCaptcha 验证码类型枚举
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.enums;
|
||||
package com.youlai.system.enums;
|
||||
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.enums;
|
||||
package com.youlai.system.enums;
|
||||
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.enums;
|
||||
package com.youlai.system.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.enums;
|
||||
package com.youlai.system.enums;
|
||||
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import lombok.Getter;
|
||||
@@ -1,17 +1,27 @@
|
||||
package com.youlai.system.core.security.jwt;
|
||||
package com.youlai.system.filter;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.jwt.JWTPayload;
|
||||
import com.youlai.system.common.constant.CacheConstants;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.common.util.ResponseUtils;
|
||||
import com.youlai.system.util.JwtUtils;
|
||||
import com.youlai.system.util.ResponseUtils;
|
||||
import com.youlai.system.common.exception.BusinessException;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT token 过滤器
|
||||
@@ -21,15 +31,6 @@ import java.io.IOException;
|
||||
*/
|
||||
public class JwtTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
/**
|
||||
* JWT Token 工具类
|
||||
*/
|
||||
private JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
|
||||
this.jwtTokenProvider = jwtTokenProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求中获取 JWT Token,校验 JWT Token 是否合法
|
||||
* <p>
|
||||
@@ -38,11 +39,21 @@ public class JwtTokenFilter extends OncePerRequestFilter {
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
String token = jwtTokenProvider.resolveToken(request);
|
||||
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
try {
|
||||
if (token != null && jwtTokenProvider.validateToken(token)) {
|
||||
Authentication auth = jwtTokenProvider.getAuthentication(token);
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
Map<String, Object> payload = JwtUtils.parseToken(token);
|
||||
String jti = Convert.toStr(payload.get(JWTPayload.JWT_ID));
|
||||
RedisTemplate redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class);
|
||||
Boolean isBlack = redisTemplate.hasKey(CacheConstants.BLACKLIST_TOKEN_PREFIX + jti);
|
||||
|
||||
if (isBlack) {
|
||||
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID);
|
||||
return;
|
||||
}
|
||||
|
||||
Authentication authentication = JwtUtils.getAuthentication(payload);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
} catch (BusinessException ex) {
|
||||
//this is very important, since it guarantees the user is not authenticated at all
|
||||
@@ -6,7 +6,7 @@ import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.youlai.system.common.constant.CacheConstants;
|
||||
import com.youlai.system.common.constant.SecurityConstants;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.common.util.ResponseUtils;
|
||||
import com.youlai.system.util.ResponseUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.youlai.system.model.bo;
|
||||
|
||||
import com.youlai.system.common.enums.MenuTypeEnum;
|
||||
import com.youlai.system.enums.MenuTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import com.youlai.system.common.base.BaseEntity;
|
||||
import com.youlai.system.common.enums.MenuTypeEnum;
|
||||
import com.youlai.system.enums.MenuTypeEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.system.model.form;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.youlai.system.common.enums.MenuTypeEnum;
|
||||
import com.youlai.system.enums.MenuTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.youlai.system.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.youlai.system.common.enums.MenuTypeEnum;
|
||||
import com.youlai.system.enums.MenuTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package com.youlai.system.plugin.dupsubmit.aspect;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.system.plugin.dupsubmit.annotation.PreventDuplicateSubmit;
|
||||
import com.youlai.system.common.result.ResultCode;
|
||||
import com.youlai.system.common.exception.BusinessException;
|
||||
import com.youlai.system.core.security.jwt.JwtTokenProvider;
|
||||
import com.youlai.system.util.JwtUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -14,6 +15,7 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
@@ -33,11 +35,6 @@ import java.util.concurrent.TimeUnit;
|
||||
public class DuplicateSubmitAspect {
|
||||
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
/**
|
||||
* JWT token 工具类
|
||||
*/
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private static final String RESUBMIT_LOCK_PREFIX = "LOCK:RESUBMIT:";
|
||||
|
||||
/**
|
||||
@@ -71,9 +68,9 @@ public class DuplicateSubmitAspect {
|
||||
String resubmitLockKey = null;
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
|
||||
String token = jwtTokenProvider.resolveToken(request);
|
||||
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
String jti = (String) jwtTokenProvider.getTokenClaims(token).get("jti");
|
||||
String jti = Convert.toStr(JwtUtils.parseToken(token).get("jti"), null);
|
||||
resubmitLockKey = RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI();
|
||||
}
|
||||
return resubmitLockKey;
|
||||
|
||||
@@ -9,8 +9,8 @@ import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.youlai.system.common.base.IBaseEnum;
|
||||
import com.youlai.system.common.constant.SystemConstants;
|
||||
import com.youlai.system.common.enums.GenderEnum;
|
||||
import com.youlai.system.common.enums.StatusEnum;
|
||||
import com.youlai.system.enums.GenderEnum;
|
||||
import com.youlai.system.enums.StatusEnum;
|
||||
import com.youlai.system.converter.UserConverter;
|
||||
import com.youlai.system.model.entity.SysRole;
|
||||
import com.youlai.system.model.entity.SysUser;
|
||||
|
||||
@@ -3,20 +3,22 @@ package com.youlai.system.service.impl;
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.jwt.JWTPayload;
|
||||
import com.youlai.system.common.constant.CacheConstants;
|
||||
import com.youlai.system.common.enums.CaptchaTypeEnum;
|
||||
import com.youlai.system.core.security.jwt.JwtTokenProvider;
|
||||
import com.youlai.system.enums.CaptchaTypeEnum;
|
||||
import com.youlai.system.model.dto.CaptchaResult;
|
||||
import com.youlai.system.model.dto.LoginResult;
|
||||
import com.youlai.system.plugin.captcha.CaptchaProperties;
|
||||
import com.youlai.system.service.AuthService;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import com.youlai.system.util.JwtUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -26,7 +28,7 @@ import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -42,7 +44,6 @@ public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final CodeGenerator codeGenerator;
|
||||
private final Font captchaFont;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
@@ -59,7 +60,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(username.toLowerCase().trim(), password);
|
||||
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
||||
String accessToken = jwtTokenProvider.createToken(authentication);
|
||||
String accessToken = JwtUtils.generateToken(authentication);
|
||||
return LoginResult.builder()
|
||||
.tokenType("Bearer")
|
||||
.accessToken(accessToken)
|
||||
@@ -72,15 +73,15 @@ public class AuthServiceImpl implements AuthService {
|
||||
@Override
|
||||
public void logout() {
|
||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
||||
String token = jwtTokenProvider.resolveToken(request);
|
||||
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (StrUtil.isNotBlank(token)) {
|
||||
Claims claims = jwtTokenProvider.getTokenClaims(token);
|
||||
String jti = claims.get("jti", String.class);
|
||||
|
||||
Date expiration = claims.getExpiration();
|
||||
Map<String, Object> claims = JwtUtils.parseToken(token);
|
||||
String jti = Convert.toStr(claims.get(JWTPayload.JWT_ID));
|
||||
Long expiration = Convert.toLong(claims.get(JWTPayload.EXPIRES_AT));
|
||||
if (expiration != null) {
|
||||
long ttl = expiration.getTime() - System.currentTimeMillis();
|
||||
redisTemplate.opsForValue().set(CacheConstants.BLACKLIST_TOKEN_PREFIX + jti, null, ttl, TimeUnit.MILLISECONDS);
|
||||
long ttl = expiration - System.currentTimeMillis() / 1000;
|
||||
redisTemplate.opsForValue().set(CacheConstants.BLACKLIST_TOKEN_PREFIX + jti, null, ttl, TimeUnit.SECONDS);
|
||||
} else {
|
||||
redisTemplate.opsForValue().set(CacheConstants.BLACKLIST_TOKEN_PREFIX + jti, null);
|
||||
}
|
||||
@@ -123,7 +124,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
|
||||
// 验证码文本缓存至Redis,用于登录校验
|
||||
String captchaKey = IdUtil.fastSimpleUUID();
|
||||
redisTemplate.opsForValue().set(CacheConstants.CAPTCHA_CODE_PREFIX + captchaKey,captchaCode,
|
||||
redisTemplate.opsForValue().set(CacheConstants.CAPTCHA_CODE_PREFIX + captchaKey, captchaCode,
|
||||
captchaProperties.getExpireSeconds(), TimeUnit.SECONDS);
|
||||
|
||||
return CaptchaResult.builder()
|
||||
|
||||
@@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.system.common.constant.SystemConstants;
|
||||
import com.youlai.system.common.enums.StatusEnum;
|
||||
import com.youlai.system.enums.StatusEnum;
|
||||
import com.youlai.system.converter.DeptConverter;
|
||||
import com.youlai.system.mapper.SysDeptMapper;
|
||||
import com.youlai.system.model.entity.SysDept;
|
||||
|
||||
@@ -7,8 +7,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.system.common.constant.SystemConstants;
|
||||
import com.youlai.system.common.enums.MenuTypeEnum;
|
||||
import com.youlai.system.common.enums.StatusEnum;
|
||||
import com.youlai.system.enums.MenuTypeEnum;
|
||||
import com.youlai.system.enums.StatusEnum;
|
||||
import com.youlai.system.common.model.Option;
|
||||
import com.youlai.system.converter.MenuConverter;
|
||||
import com.youlai.system.mapper.SysMenuMapper;
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.youlai.system.model.vo.RolePageVO;
|
||||
import com.youlai.system.service.SysRoleMenuService;
|
||||
import com.youlai.system.service.SysRoleService;
|
||||
import com.youlai.system.service.SysUserRoleService;
|
||||
import com.youlai.system.common.util.SecurityUtils;
|
||||
import com.youlai.system.util.SecurityUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -8,12 +8,10 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.youlai.system.common.constant.CacheConstants;
|
||||
import com.youlai.system.common.constant.SecurityConstants;
|
||||
import com.youlai.system.common.constant.SystemConstants;
|
||||
import com.youlai.system.common.util.DateUtils;
|
||||
import com.youlai.system.util.DateUtils;
|
||||
import com.youlai.system.converter.UserConverter;
|
||||
import com.youlai.system.common.util.SecurityUtils;
|
||||
import com.youlai.system.util.SecurityUtils;
|
||||
import com.youlai.system.mapper.SysUserMapper;
|
||||
import com.youlai.system.model.dto.UserAuthInfo;
|
||||
import com.youlai.system.model.bo.UserBO;
|
||||
@@ -32,7 +30,6 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
package com.youlai.system.common.util;
|
||||
package com.youlai.system.util;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.util;
|
||||
package com.youlai.system.util;
|
||||
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.youlai.system.plugin.easyexcel.MyAnalysisEventListener;
|
||||
152
src/main/java/com/youlai/system/util/JwtUtils.java
Normal file
152
src/main/java/com/youlai/system/util/JwtUtils.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package com.youlai.system.util;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTPayload;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
import com.youlai.system.common.constant.JwtClaimConstants;
|
||||
import com.youlai.system.core.security.model.SysUserDetails;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT 工具类
|
||||
*
|
||||
* @author haoxr
|
||||
* @since 2.6.0
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
private static byte[] key;
|
||||
|
||||
|
||||
private static int ttl;
|
||||
|
||||
|
||||
@Value("${jwt.key}")
|
||||
public void setKey(String key) {
|
||||
JwtUtils.key = key.getBytes();
|
||||
}
|
||||
|
||||
@Value("${jwt.ttl}")
|
||||
public void setTtl(Integer ttl) {
|
||||
JwtUtils.ttl = ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Token
|
||||
* <p>
|
||||
* 认证成功后的用户信息会被封装到 Authentication 对象中,然后通过 JwtTokenProvider#createToken(Authentication) 方法创建 Token 字符串
|
||||
*
|
||||
* @param authentication 用户认证信息
|
||||
* @return Token 字符串
|
||||
*/
|
||||
public static String generateToken(Authentication authentication) {
|
||||
|
||||
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
|
||||
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID
|
||||
payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID
|
||||
payload.put(JwtClaimConstants.DATA_SCOPE, userDetails.getDataScope()); // 数据权限范围
|
||||
|
||||
// claims 中添加角色信息
|
||||
Set<String> roles = userDetails.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toSet());
|
||||
payload.put(JwtClaimConstants.AUTHORITIES, roles);
|
||||
|
||||
|
||||
Date now = new Date();
|
||||
Date expiration = DateUtil.offsetSecond(now, ttl);
|
||||
payload.put(JWTPayload.ISSUED_AT, now);
|
||||
payload.put(JWTPayload.EXPIRES_AT, expiration);
|
||||
payload.put(JWTPayload.SUBJECT, authentication.getName());
|
||||
payload.put(JWTPayload.JWT_ID, IdUtil.simpleUUID());
|
||||
|
||||
return JWTUtil.createToken(payload, JwtUtils.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中解析数据
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 解析数据
|
||||
*/
|
||||
public static Map<String, Object> parseToken(String token) {
|
||||
try {
|
||||
if (StrUtil.isBlank(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token.startsWith("Bearer ")) {
|
||||
token = token.substring(7);
|
||||
}
|
||||
|
||||
JWT jwt = JWTUtil.parseToken(token);
|
||||
if (jwt.setKey(JwtUtils.key).validate(0)) {
|
||||
return jwt.getPayloads();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Token 中获取 Authentication
|
||||
*
|
||||
* @param payload
|
||||
* @return
|
||||
*/
|
||||
public static UsernamePasswordAuthenticationToken getAuthentication(Map<String, Object> payload) {
|
||||
SysUserDetails userDetails = new SysUserDetails();
|
||||
userDetails.setUserId(Convert.toLong(payload.get(JwtClaimConstants.USER_ID))); // 用户ID
|
||||
userDetails.setDeptId(Convert.toLong(payload.get(JwtClaimConstants.DEPT_ID))); // 部门ID
|
||||
userDetails.setDataScope(Convert.toInt(payload.get(JwtClaimConstants.DATA_SCOPE))); // 数据权限范围
|
||||
|
||||
userDetails.setUsername(Convert.toStr(payload.get(JWTPayload.SUBJECT))); // 用户名
|
||||
// 角色集合
|
||||
Set<SimpleGrantedAuthority> authorities = ((JSONArray) payload.get(JwtClaimConstants.AUTHORITIES))
|
||||
.stream()
|
||||
.map(authority -> new SimpleGrantedAuthority(Convert.toStr(authority)))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证 JWT Token
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 是否有效
|
||||
*/
|
||||
public static boolean verifyToken(String token) {
|
||||
|
||||
if (StrUtil.isBlank(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (token.startsWith("Bearer ")) {
|
||||
token = token.substring(7);
|
||||
}
|
||||
|
||||
JWT jwt = JWTUtil.parseToken(token);
|
||||
return jwt.setKey(JwtUtils.key).validate(0);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.util;
|
||||
package com.youlai.system.util;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.youlai.system.common.result.IResultCode;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.util;
|
||||
package com.youlai.system.util;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
@@ -49,8 +49,7 @@ public class SecurityUtils {
|
||||
* @return
|
||||
*/
|
||||
public static Long getDeptId() {
|
||||
Long userId = Convert.toLong(getUser().getDeptId());
|
||||
return userId;
|
||||
return Convert.toLong(getUser().getDeptId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,8 +58,7 @@ public class SecurityUtils {
|
||||
* @return DataScope
|
||||
*/
|
||||
public static Integer getDataScope() {
|
||||
Integer dataScope = Convert.toInt(getUser().getDataScope());
|
||||
return dataScope;
|
||||
return Convert.toInt(getUser().getDataScope());
|
||||
}
|
||||
|
||||
|
||||
@@ -74,10 +72,9 @@ public class SecurityUtils {
|
||||
if (authentication != null) {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
if (CollectionUtil.isNotEmpty(authorities)) {
|
||||
Set<String> roles = authorities.stream().filter(item -> item.getAuthority().startsWith("ROLE_"))
|
||||
return authorities.stream().filter(item -> item.getAuthority().startsWith("ROLE_"))
|
||||
.map(item -> StrUtil.removePrefix(item.getAuthority(), "ROLE_"))
|
||||
.collect(Collectors.toSet());
|
||||
return roles;
|
||||
}
|
||||
}
|
||||
return Collections.EMPTY_SET;
|
||||
@@ -93,10 +90,10 @@ public class SecurityUtils {
|
||||
if (authentication != null) {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
if (CollectionUtil.isNotEmpty(authorities)) {
|
||||
Set<String> perms = authorities.stream().filter(item -> !item.getAuthority().startsWith("ROLE_"))
|
||||
.map(item -> item.getAuthority())
|
||||
return authorities.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.filter(authority -> !authority.startsWith("ROLE_"))
|
||||
.collect(Collectors.toSet());
|
||||
return perms;
|
||||
}
|
||||
}
|
||||
return Collections.EMPTY_SET;
|
||||
@@ -111,11 +108,7 @@ public class SecurityUtils {
|
||||
*/
|
||||
public static boolean isRoot() {
|
||||
Set<String> roles = getRoles();
|
||||
|
||||
if (roles.contains(SystemConstants.ROOT_ROLE_CODE)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return roles.contains(SystemConstants.ROOT_ROLE_CODE);
|
||||
}
|
||||
|
||||
|
||||
@@ -134,8 +127,7 @@ public class SecurityUtils {
|
||||
|
||||
Set<String> perms = getPerms();
|
||||
|
||||
boolean hasPerm = perms.stream().anyMatch(item -> PatternMatchUtils.simpleMatch(perm, item));
|
||||
return hasPerm;
|
||||
return perms.stream().anyMatch(item -> PatternMatchUtils.simpleMatch(perm, item));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user