Merge branch 'master' of gitee.com:youlaiorg/youlai-boot

This commit is contained in:
2026-06-15 09:34:29 +08:00
10 changed files with 48 additions and 15 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View File

@@ -6,7 +6,7 @@
<groupId>com.youlai</groupId>
<artifactId>youlai-boot</artifactId>
<version>4.3.0</version>
<version>4.3.1</version>
<description>基于 Java 17 + SpringBoot 4 + Spring Security 构建的权限管理系统。</description>
<parent>

View File

@@ -3,13 +3,13 @@ package com.youlai.boot.framework.security.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 验证码校验异常
* 短信验证码异常
*
* @author Ray.Hao
* @since 2025/3/1
*/
public class CaptchaValidationException extends AuthenticationException {
public CaptchaValidationException(String msg) {
public class SmsCaptchaException extends AuthenticationException {
public SmsCaptchaException(String msg) {
super(msg);
}
}
}

View File

@@ -0,0 +1,21 @@
package com.youlai.boot.framework.security.exception;
import com.youlai.boot.common.result.ResultCode;
import lombok.Getter;
/**
* Token 无效异常access_token 或 refresh_token 过期/无效)
*
* @author Ray.Hao
* @since 4.3.1
*/
@Getter
public class TokenInvalidException extends RuntimeException {
private final ResultCode resultCode;
public TokenInvalidException(ResultCode resultCode) {
super(resultCode.getMsg());
this.resultCode = resultCode;
}
}

View File

@@ -3,7 +3,7 @@ package com.youlai.boot.framework.security.provider;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.security.exception.CaptchaValidationException;
import com.youlai.boot.framework.security.exception.SmsCaptchaException;
import com.youlai.boot.framework.security.model.SmsAuthenticationToken;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.model.UserAuthInfo;
@@ -62,11 +62,11 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
// 参数校验
if (StrUtil.isBlank(mobile)) {
log.warn("短信验证码登录失败:手机号为空");
throw new CaptchaValidationException("手机号不能为空");
throw new SmsCaptchaException("手机号不能为空");
}
if (StrUtil.isBlank(inputVerifyCode)) {
log.warn("短信验证码登录失败:验证码为空,手机号={}", mobile);
throw new CaptchaValidationException("验证码不能为空");
throw new SmsCaptchaException("验证码不能为空");
}
// 根据手机号获取用户信息
@@ -89,12 +89,12 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
if (cachedVerifyCode == null) {
log.warn("短信验证码登录失败:验证码已过期,手机号={}", mobile);
throw new CaptchaValidationException("验证码已过期,请重新获取");
throw new SmsCaptchaException("验证码已过期,请重新获取");
}
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
log.warn("短信验证码登录失败:验证码错误,手机号={}", mobile);
throw new CaptchaValidationException("验证码错误");
throw new SmsCaptchaException("验证码错误");
}
// 验证成功后删除验证码,防止重复使用

View File

@@ -12,9 +12,9 @@ import cn.hutool.jwt.JWTUtil;
import com.youlai.boot.common.constant.JwtClaimConstants;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.framework.security.config.SecurityProperties;
import com.youlai.boot.framework.security.exception.TokenInvalidException;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import com.youlai.boot.framework.security.model.RoleDataScope;
import org.apache.commons.lang3.StringUtils;
@@ -300,7 +300,7 @@ public class JwtTokenManager implements TokenManager {
public AuthenticationToken refreshToken(String refreshToken) {
boolean isValid = validateRefreshToken(refreshToken);
if (!isValid) {
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
throw new TokenInvalidException(ResultCode.REFRESH_TOKEN_INVALID);
}
Authentication authentication = parseToken(refreshToken);
int accessTokenExpiration = securityProperties.getSession().getAccessTokenTimeToLive();

View File

@@ -5,9 +5,9 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.framework.security.config.SecurityProperties;
import com.youlai.boot.framework.security.exception.TokenInvalidException;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import com.youlai.boot.framework.security.model.UserSession;
import com.youlai.boot.framework.security.model.SysUserDetails;
@@ -147,7 +147,7 @@ public class RedisTokenManager implements TokenManager {
UserSession userSession = (UserSession) redisTemplate.opsForValue()
.get(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken));
if (userSession == null) {
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
throw new TokenInvalidException(ResultCode.REFRESH_TOKEN_INVALID);
}
Object oldAccessTokenValue = redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userSession.getUserId()));
// 删除旧的访问令牌记录

View File

@@ -5,6 +5,7 @@ import tools.jackson.core.JacksonException;
import com.youlai.boot.common.exception.BusinessException;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.framework.security.exception.TokenInvalidException;
import jakarta.servlet.ServletException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
@@ -222,6 +223,17 @@ public class GlobalExceptionHandler {
return Result.failed(ResultCode.INTEGRITY_CONSTRAINT_VIOLATION);
}
/**
* 处理 Token 无效异常
* <p>
* 当 access_token 或 refresh_token 过期/无效时,返回 401。
*/
@ExceptionHandler(TokenInvalidException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public <T> Result<T> handleTokenInvalidException(TokenInvalidException e) {
return Result.failed(e.getResultCode());
}
/**
* 处理业务异常
* <p>

View File

@@ -27,7 +27,7 @@
#else
<if test="queryParams.${fieldConfig.fieldName} != null">
#end
#set ($queryType = ${fieldConfig.queryType}.name())
#set ($queryType = $fieldConfig.queryType.name())
#if($queryType == "EQ")
AND ${fieldConfig.columnName} = #{queryParams.${fieldConfig.fieldName}}
#elseif($queryType == "LIKE")