refactor: 登录认证代码优化

This commit is contained in:
haoxr
2023-03-27 23:05:47 +08:00
parent 54f1c4efac
commit 3a10d65c9b
5 changed files with 35 additions and 36 deletions

View File

@@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -29,6 +30,7 @@ import java.util.concurrent.TimeUnit;
@RestController @RestController
@RequestMapping("/api/v1/auth") @RequestMapping("/api/v1/auth")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class AuthController { public class AuthController {
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
private final JwtTokenManager jwtTokenManager; private final JwtTokenManager jwtTokenManager;
@@ -46,7 +48,6 @@ public class AuthController {
password password
); );
Authentication authentication = authenticationManager.authenticate(authenticationToken); Authentication authentication = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成token // 生成token
String accessToken = jwtTokenManager.createToken(authentication); String accessToken = jwtTokenManager.createToken(authentication);
LoginResult loginResult = LoginResult.builder() LoginResult loginResult = LoginResult.builder()
@@ -56,9 +57,9 @@ public class AuthController {
return Result.success(loginResult); return Result.success(loginResult);
} }
@Operation(summary = "注销", security = {@SecurityRequirement(name = "Authorization")}) @Operation(summary = "注销", security = {@SecurityRequirement(name = SecurityConstants.TOKEN_KEY)})
@DeleteMapping("/logout") @DeleteMapping("/logout")
public Result login(HttpServletRequest request) { public Result logout(HttpServletRequest request) {
String token = RequestUtils.resolveToken(request); String token = RequestUtils.resolveToken(request);
if (StrUtil.isNotBlank(token)) { if (StrUtil.isNotBlank(token)) {
Claims claims = jwtTokenManager.getTokenClaims(token); Claims claims = jwtTokenManager.getTokenClaims(token);
@@ -67,7 +68,7 @@ public class AuthController {
Date expiration = claims.getExpiration(); Date expiration = claims.getExpiration();
if (expiration != null) { if (expiration != null) {
// 有过期时间在token有效时间内存入黑名单超出时间移除黑名单节省内存占用 // 有过期时间在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); redisTemplate.opsForValue().set(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + jti, null, ttl, TimeUnit.MILLISECONDS);
} else { } else {
// 无过期时间,永久加入黑名单 // 无过期时间,永久加入黑名单

View File

@@ -7,7 +7,7 @@ import com.youlai.system.framework.easycaptcha.config.EasyCaptchaConfig;
import com.youlai.system.framework.easycaptcha.producer.EasyCaptchaProducer; import com.youlai.system.framework.easycaptcha.producer.EasyCaptchaProducer;
import com.youlai.system.pojo.dto.CaptchaResult; import com.youlai.system.pojo.dto.CaptchaResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -24,7 +24,7 @@ public class EasyCaptchaService {
private final EasyCaptchaProducer easyCaptchaProducer; private final EasyCaptchaProducer easyCaptchaProducer;
private final StringRedisTemplate redisTemplate; private final RedisTemplate redisTemplate;
private final EasyCaptchaConfig easyCaptchaConfig; private final EasyCaptchaConfig easyCaptchaConfig;

View File

@@ -63,6 +63,7 @@ public class JwtTokenManager {
public String createToken(Authentication authentication) { public String createToken(Authentication authentication) {
Claims claims = Jwts.claims().setSubject(authentication.getName()); Claims claims = Jwts.claims().setSubject(authentication.getName());
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
claims.put("jti",IdUtil.fastSimpleUUID());
claims.put("userId", userDetails.getUserId()); claims.put("userId", userDetails.getUserId());
claims.put("username", claims.getSubject()); claims.put("username", claims.getSubject());
claims.put("deptId", userDetails.getDeptId()); claims.put("deptId", userDetails.getDeptId());
@@ -72,7 +73,6 @@ public class JwtTokenManager {
Set<String> roles = userDetails.getAuthorities().stream() Set<String> roles = userDetails.getAuthorities().stream()
.map(item -> item.getAuthority()).collect(Collectors.toSet()); .map(item -> item.getAuthority()).collect(Collectors.toSet());
claims.put("authorities", roles); claims.put("authorities", roles);
claims.put("jti",IdUtil.fastSimpleUUID());
// 权限数据多放入Redis // 权限数据多放入Redis
Set<String> perms = userDetails.getPerms(); Set<String> perms = userDetails.getPerms();
@@ -90,9 +90,7 @@ public class JwtTokenManager {
/** /**
* 获取认证信息 * 获取认证信息
*/ */
public Authentication getAuthentication(String token) { public Authentication getAuthentication( Claims claims) {
Claims claims = this.getTokenClaims(token);
SysUserDetails principal = new SysUserDetails(); SysUserDetails principal = new SysUserDetails();
principal.setUserId(Convert.toLong(claims.get("userId"))); // 用户ID principal.setUserId(Convert.toLong(claims.get("userId"))); // 用户ID
principal.setUsername(Convert.toStr(claims.get("username"))); // 用户名 principal.setUsername(Convert.toStr(claims.get("username"))); // 用户名
@@ -108,21 +106,18 @@ public class JwtTokenManager {
} }
/** /**
* 验证 token * 解析 & 验证 token
*/ */
public void validateToken(String token) { public Claims parseAndValidateToken(String token) {
if (jwtParser == null) { // 解析成功说明JWT有效
jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build(); Claims claims = this.getTokenClaims(token);
} // 验证JWT 是否在黑名单(注销场景会存入黑名单)
// 验证 JWT无异常解析成功说明JWT有效
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
// 验证 JWT 是否在黑名单(注销场景会存入黑名单)
Claims claims = claimsJws.getBody();
Boolean isBlack = redisTemplate.hasKey(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + claims.get("jti")); Boolean isBlack = redisTemplate.hasKey(SecurityConstants.BLACK_TOKEN_CACHE_PREFIX + claims.get("jti"));
if (isBlack) { if (isBlack) {
throw new RuntimeException("token 已被禁用"); throw new RuntimeException("token 已被禁用");
} }
return claims;
} }
public byte[] getSecretKeyBytes() { public byte[] getSecretKeyBytes() {

View File

@@ -6,12 +6,14 @@ import com.youlai.system.common.result.ResultCode;
import com.youlai.system.common.util.RequestUtils; import com.youlai.system.common.util.RequestUtils;
import com.youlai.system.common.util.ResponseUtils; import com.youlai.system.common.util.ResponseUtils;
import com.youlai.system.framework.security.JwtTokenManager; import com.youlai.system.framework.security.JwtTokenManager;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.io.IOException;
@@ -24,6 +26,8 @@ import java.io.IOException;
*/ */
public class JwtAuthenticationFilter extends OncePerRequestFilter { public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST");
private final JwtTokenManager tokenManager; private final JwtTokenManager tokenManager;
public JwtAuthenticationFilter(JwtTokenManager tokenManager) { public JwtAuthenticationFilter(JwtTokenManager tokenManager) {
@@ -32,18 +36,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 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); chain.doFilter(request, response);
}else{ }else{
String jwt = RequestUtils.resolveToken(request); String jwt = RequestUtils.resolveToken(request);
if (StrUtil.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) { if (StrUtil.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
try { try {
// 验证 JWT // 解析&验证 JWT
this.tokenManager.validateToken(jwt); Claims claims = this.tokenManager.parseAndValidateToken(jwt);
// JWT验证有效获取Authentication存入Security上下文 // JWT验证有效获取Authentication存入Security上下文
Authentication authentication = this.tokenManager.getAuthentication(jwt); Authentication authentication = this.tokenManager.getAuthentication(claims);
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response); chain.doFilter(request, response);

View File

@@ -3,19 +3,20 @@ package com.youlai.system.framework.security.filter;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil; 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.result.ResultCode;
import com.youlai.system.common.util.ResponseUtils; import com.youlai.system.common.util.ResponseUtils;
import com.youlai.system.common.constant.SecurityConstants;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate; 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 org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.io.IOException;
/** /**
* 验证码校验过滤器 * 验证码校验过滤器
* *
@@ -24,21 +25,17 @@ import java.io.IOException;
*/ */
public class VerifyCodeFilter extends OncePerRequestFilter { public class VerifyCodeFilter extends OncePerRequestFilter {
public static final String VERIFY_CODE = "verifyCode"; private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST");
public static final String VERIFY_CODE_KEY = "verifyCodeKey";
RedisTemplate redisTemplate; public static final String VERIFY_CODE_PARAM_KEY = "verifyCode";
public static final String VERIFY_CODE_KEY_PARAM_KEY = "verifyCodeKey";
public VerifyCodeFilter() {
this.redisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
}
@Override @Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { 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 无验证码版本,后续移除 // TODO 兼容 2.0.0 无验证码版本,后续移除
if (StrUtil.isBlank(requestVerifyCode)) { if (StrUtil.isBlank(requestVerifyCode)) {
@@ -47,7 +44,8 @@ public class VerifyCodeFilter extends OncePerRequestFilter {
return; 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); Object cacheVerifyCode = redisTemplate.opsForValue().get(SecurityConstants.VERIFY_CODE_CACHE_PREFIX + verifyCodeKey);
if (cacheVerifyCode == null) { if (cacheVerifyCode == null) {
ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT); ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT);