diff --git a/src/main/java/com/youlai/system/controller/AuthController.java b/src/main/java/com/youlai/system/controller/AuthController.java index fdd26cfb..aa901606 100644 --- a/src/main/java/com/youlai/system/controller/AuthController.java +++ b/src/main/java/com/youlai/system/controller/AuthController.java @@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -29,6 +30,7 @@ import java.util.concurrent.TimeUnit; @RestController @RequestMapping("/api/v1/auth") @RequiredArgsConstructor +@Slf4j public class AuthController { private final AuthenticationManager authenticationManager; private final JwtTokenManager jwtTokenManager; @@ -46,7 +48,6 @@ public class AuthController { password ); Authentication authentication = authenticationManager.authenticate(authenticationToken); - SecurityContextHolder.getContext().setAuthentication(authentication); // 生成token String accessToken = jwtTokenManager.createToken(authentication); LoginResult loginResult = LoginResult.builder() @@ -56,9 +57,9 @@ public class AuthController { return Result.success(loginResult); } - @Operation(summary = "注销", security = {@SecurityRequirement(name = "Authorization")}) + @Operation(summary = "注销", security = {@SecurityRequirement(name = SecurityConstants.TOKEN_KEY)}) @DeleteMapping("/logout") - public Result login(HttpServletRequest request) { + public Result logout(HttpServletRequest request) { String token = RequestUtils.resolveToken(request); if (StrUtil.isNotBlank(token)) { Claims claims = jwtTokenManager.getTokenClaims(token); @@ -67,7 +68,7 @@ public class AuthController { Date expiration = claims.getExpiration(); if (expiration != null) { // 有过期时间,在token有效时间内存入黑名单,超出时间移除黑名单节省内存占用 - long ttl = (expiration.getTime() - System.currentTimeMillis()) ; + long ttl = (expiration.getTime() - System.currentTimeMillis()); redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null, ttl, TimeUnit.MILLISECONDS); } else { // 无过期时间,永久加入黑名单 diff --git a/src/main/java/com/youlai/system/framework/easycaptcha/service/EasyCaptchaService.java b/src/main/java/com/youlai/system/framework/easycaptcha/service/EasyCaptchaService.java index d81bae64..44318326 100644 --- a/src/main/java/com/youlai/system/framework/easycaptcha/service/EasyCaptchaService.java +++ b/src/main/java/com/youlai/system/framework/easycaptcha/service/EasyCaptchaService.java @@ -7,7 +7,7 @@ import com.youlai.system.framework.easycaptcha.config.EasyCaptchaConfig; import com.youlai.system.framework.easycaptcha.producer.EasyCaptchaProducer; import com.youlai.system.pojo.dto.CaptchaResult; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @@ -24,7 +24,7 @@ public class EasyCaptchaService { private final EasyCaptchaProducer easyCaptchaProducer; - private final StringRedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; private final EasyCaptchaConfig easyCaptchaConfig; diff --git a/src/main/java/com/youlai/system/framework/security/JwtTokenManager.java b/src/main/java/com/youlai/system/framework/security/JwtTokenManager.java index 77000867..8df1507f 100644 --- a/src/main/java/com/youlai/system/framework/security/JwtTokenManager.java +++ b/src/main/java/com/youlai/system/framework/security/JwtTokenManager.java @@ -63,6 +63,7 @@ public class JwtTokenManager { public String createToken(Authentication authentication) { Claims claims = Jwts.claims().setSubject(authentication.getName()); SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); + claims.put("jti",IdUtil.fastSimpleUUID()); claims.put("userId", userDetails.getUserId()); claims.put("username", claims.getSubject()); claims.put("deptId", userDetails.getDeptId()); @@ -72,7 +73,6 @@ public class JwtTokenManager { Set roles = userDetails.getAuthorities().stream() .map(item -> item.getAuthority()).collect(Collectors.toSet()); claims.put("authorities", roles); - claims.put("jti",IdUtil.fastSimpleUUID()); // 权限数据多放入Redis Set perms = userDetails.getPerms(); @@ -90,9 +90,7 @@ public class JwtTokenManager { /** * 获取认证信息 */ - public Authentication getAuthentication(String token) { - Claims claims = this.getTokenClaims(token); - + public Authentication getAuthentication( Claims claims) { SysUserDetails principal = new SysUserDetails(); principal.setUserId(Convert.toLong(claims.get("userId"))); // 用户ID principal.setUsername(Convert.toStr(claims.get("username"))); // 用户名 @@ -108,21 +106,18 @@ public class JwtTokenManager { } /** - * 验证 token + * 解析 & 验证 token */ - public void validateToken(String token) { - if (jwtParser == null) { - jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build(); - } - // 验证 JWT,无异常解析成功说明JWT有效 - Jws claimsJws = jwtParser.parseClaimsJws(token); - // 验证 JWT 是否在黑名单(注销场景会存入黑名单) - Claims claims = claimsJws.getBody(); + public Claims parseAndValidateToken(String token) { + // 解析成功说明JWT有效 + Claims claims = this.getTokenClaims(token); + // 验证JWT 是否在黑名单(注销场景会存入黑名单) Boolean isBlack = redisTemplate.hasKey(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + claims.get("jti")); if (isBlack) { throw new RuntimeException("token 已被禁用"); } + return claims; } public byte[] getSecretKeyBytes() { diff --git a/src/main/java/com/youlai/system/framework/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/youlai/system/framework/security/filter/JwtAuthenticationFilter.java index 8b9be6ff..237c7717 100644 --- a/src/main/java/com/youlai/system/framework/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/youlai/system/framework/security/filter/JwtAuthenticationFilter.java @@ -6,12 +6,14 @@ import com.youlai.system.common.result.ResultCode; import com.youlai.system.common.util.RequestUtils; import com.youlai.system.common.util.ResponseUtils; import com.youlai.system.framework.security.JwtTokenManager; +import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -24,6 +26,8 @@ import java.io.IOException; */ public class JwtAuthenticationFilter extends OncePerRequestFilter { + private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST"); + private final JwtTokenManager tokenManager; public JwtAuthenticationFilter(JwtTokenManager tokenManager) { @@ -32,18 +36,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - if (SecurityConstants.LOGIN_PATH.equals(request.getRequestURI())) { - // 登录接口放行 + // 登录接口放行是走过滤器链的方式(验证码校验过滤器),这里拦截到登录接口需要手动放行 + if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) { + // 手动放行登录接口 chain.doFilter(request, response); }else{ String jwt = RequestUtils.resolveToken(request); if (StrUtil.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) { try { - // 验证 JWT - this.tokenManager.validateToken(jwt); + // 解析&验证 JWT + Claims claims = this.tokenManager.parseAndValidateToken(jwt); // JWT验证有效获取Authentication存入Security上下文 - Authentication authentication = this.tokenManager.getAuthentication(jwt); + Authentication authentication = this.tokenManager.getAuthentication(claims); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response); diff --git a/src/main/java/com/youlai/system/framework/security/filter/VerifyCodeFilter.java b/src/main/java/com/youlai/system/framework/security/filter/VerifyCodeFilter.java index b8c3b216..94b885a9 100644 --- a/src/main/java/com/youlai/system/framework/security/filter/VerifyCodeFilter.java +++ b/src/main/java/com/youlai/system/framework/security/filter/VerifyCodeFilter.java @@ -3,19 +3,20 @@ package com.youlai.system.framework.security.filter; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; +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.common.constant.SecurityConstants; 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.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; + /** * 验证码校验过滤器 * @@ -24,21 +25,17 @@ import java.io.IOException; */ public class VerifyCodeFilter extends OncePerRequestFilter { - public static final String VERIFY_CODE = "verifyCode"; - public static final String VERIFY_CODE_KEY = "verifyCodeKey"; + private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST"); - RedisTemplate redisTemplate; - - public VerifyCodeFilter() { - this.redisTemplate = SpringUtil.getBean(StringRedisTemplate.class); - } + public static final String VERIFY_CODE_PARAM_KEY = "verifyCode"; + public static final String VERIFY_CODE_KEY_PARAM_KEY = "verifyCodeKey"; @Override public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 检验登录接口的验证码 - if (SecurityConstants.LOGIN_PATH.equals(request.getRequestURI())) { + if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) { // 请求中的验证码 - String requestVerifyCode = request.getParameter(VERIFY_CODE); + String requestVerifyCode = request.getParameter(VERIFY_CODE_PARAM_KEY); // TODO 兼容 2.0.0 无验证码版本,后续移除 if (StrUtil.isBlank(requestVerifyCode)) { @@ -47,7 +44,8 @@ public class VerifyCodeFilter extends OncePerRequestFilter { return; } // 缓存中的验证码 - String verifyCodeKey = request.getParameter(VERIFY_CODE_KEY); + RedisTemplate redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class); + String verifyCodeKey = request.getParameter(VERIFY_CODE_KEY_PARAM_KEY); Object cacheVerifyCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_CACHE_PREFIX + verifyCodeKey); if (cacheVerifyCode == null) { ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT);