wip: 临时提交

This commit is contained in:
Ray.Hao
2025-03-07 08:20:53 +08:00
parent 3209b0b3ae
commit a84f2b9988
14 changed files with 86 additions and 89 deletions

View File

@@ -1,7 +1,7 @@
package com.youlai.boot.common.constant;
/**
* Redis Key常量
* Redis 常量
*
* @author Theo
* @since 2024-7-29 11:46:08
@@ -9,32 +9,41 @@ package com.youlai.boot.common.constant;
public interface RedisConstants {
/**
* 系统配置 Redis
* 限流相关
*/
String SYSTEM_CONFIG_KEY = "system:config";
interface RateLimiter {
String IP = "rate_limiter:ip:{}"; // IP限流示例rate_limiter:ip:192.168.1.1
}
/**
* 分布式锁相关键
*/
interface Lock {
String RESUBMIT = "lock:resubmit:{}:{}"; // 防重复提交示例lock:resubmit:methodName:md5Hash
}
/**
* 认证模块
*/
interface Auth {
String BLACKLIST_TOKEN = "auth:token:blacklist:{}"; // 黑名单Token
}
/**
* IP 限流 Redis 键
* 验证码模块
*/
String IP_RATE_LIMITER_KEY = "rate:limiter:ip:";
interface Captcha {
String IMAGE_CODE = "captcha:image:{}"; // 图形验证码
String SMS_LOGIN_CODE = "captcha:sms_login:{}"; // 登录短信验证码
String SMS_REGISTER_CODE = "captcha:sms_register:{}";// 注册短信验证码
String MOBILE_CODE = "captcha:mobile:{}"; // 绑定、更换手机验证码
String EMAIL_CODE = "captcha:email:{}"; // 邮箱验证码
}
/**
* 防重复提交 Redis 键前缀
* 系统模块
*/
String RESUBMIT_LOCK_PREFIX = "lock:resubmit:";
interface System {
String CONFIG = "system:config"; // 系统配置
String ROLE_PERMS = "system:role:perms"; // 系统角色和权限映射
}
/**
* 登录手机验证码 Redis 键前缀
*/
String SMS_LOGIN_CODE_PREFIX= "code:sms:login:";
/**
* 绑定或更换手机号验证码 Redis 键前缀
*/
String SMS_CHANGE_CODE_PREFIX = "code:sms:change:";
/**
* 绑定或更换邮箱验证码 Redis 键前缀
*/
String EMAIL_CHANGE_CODE_PREFIX = "code:email:change:";
}

View File

@@ -8,21 +8,6 @@ package com.youlai.boot.common.constant;
*/
public interface SecurityConstants {
/**
* 验证码缓存前缀
*/
String CAPTCHA_CODE_PREFIX = "captcha_code:";
/**
* 角色和权限缓存前缀
*/
String ROLE_PERMS_PREFIX = "role_perms:";
/**
* 黑名单Token缓存前缀
*/
String BLACKLIST_TOKEN_PREFIX = "token:blacklist:";
/**
* 登录路径
*/
@@ -31,7 +16,7 @@ public interface SecurityConstants {
/**
* JWT Token 前缀
*/
String JWT_TOKEN_PREFIX = "Bearer ";
String BEARER_TOKEN_PREFIX = "Bearer ";
/**
* 角色前缀,用于区分 authorities 角色和权限, ROLE_* 角色 、没有前缀的是权限

View File

@@ -85,7 +85,7 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String bearerToken = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION);
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
bearerToken = bearerToken.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
bearerToken = bearerToken.substring(SecurityConstants.BEARER_TOKEN_PREFIX .length());
String username = JWTUtil.parseToken(bearerToken).getPayloads().getStr(JWTPayload.SUBJECT);
if (StrUtil.isNotBlank(username)) {
accessor.setUser(() -> username);

View File

@@ -1,6 +1,7 @@
package com.youlai.boot.core.aspect;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.RegisteredPayload;
import com.youlai.boot.common.constant.RedisConstants;
@@ -74,31 +75,23 @@ public class RepeatSubmitAspect {
}
/**
* 生成防重复提交锁的 key
*
* @return 锁的 key如果无法生成则返回 null
*/
private String buildLockKey() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
String tokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StrUtil.isBlank(token) || !token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
log.warn("请求头中未找到有效的 JWT Token");
// 统一校验 Token 格式
if (StrUtil.isBlank(tokenHeader) || !tokenHeader.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX )) {
log.warn("请求头中未找到有效的 Token");
return null;
}
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
// 如果会话方式是jwt解析 JWT Token 获取 jti
if (securityProperties.getSession().getType().equals("jwt")) {
String jti = (String) JWTUtil.parseToken(token).getPayload(RegisteredPayload.JWT_ID);
if (StrUtil.isBlank(jti)) {
log.warn("JWT Token 中未找到 jti");
return null;
}
}
// 否则会话方式为redis-token直接使用token
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
return RedisConstants.RESUBMIT_LOCK_PREFIX + token + ":" + request.getMethod() + "-" + request.getRequestURI();
String rawToken = tokenHeader.substring(SecurityConstants.BEARER_TOKEN_PREFIX .length());
String tokenHash = DigestUtil.sha256Hex(rawToken); // 建议替换为 SHA256 更安全
return RedisConstants.RESUBMIT_LOCK_PREFIX
+ tokenHash + ":"
+ request.getMethod() + "-"
+ request.getRequestURI();
}
}

View File

@@ -1,6 +1,7 @@
package com.youlai.boot.core.filter;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SystemConstants;
import com.youlai.boot.common.result.ResultCode;
@@ -48,7 +49,7 @@ public class RateLimiterFilter extends OncePerRequestFilter {
*/
public boolean rateLimit(String ip) {
// 限流 Redis 键
String key = RedisConstants.IP_RATE_LIMITER_KEY + ip;
String key = StrUtil.format(RedisConstants.RateLimiter.IP, ip);
// 自增请求计数
Long count = redisTemplate.opsForValue().increment(key);

View File

@@ -61,13 +61,13 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
}
// 校验发送短信验证码的手机号是否与当前登录用户一致
String cachedVerifyCode = (String) redisTemplate.opsForValue().get(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile);
String cachedVerifyCode = (String) redisTemplate.opsForValue().get(RedisConstants.Captcha.SMS_LOGIN_CODE + mobile);
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
throw new CaptchaValidationException("验证码错误");
} else {
// 验证成功后删除验证码
redisTemplate.delete(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile);
redisTemplate.delete(RedisConstants.Captcha.SMS_LOGIN_CODE + mobile);
}
// 构建认证后的用户详情信息

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.core.security.filter;
import cn.hutool.captcha.generator.CodeGenerator;
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.result.ResultCode;
import com.youlai.boot.common.util.ResponseUtils;
@@ -53,7 +54,9 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
}
// 缓存中的验证码
String verifyCodeKey = request.getParameter(CAPTCHA_KEY_PARAM_NAME);
String cacheVerifyCode = (String) redisTemplate.opsForValue().get(SecurityConstants.CAPTCHA_CODE_PREFIX + verifyCodeKey);
String cacheVerifyCode = (String) redisTemplate.opsForValue().get(
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, verifyCodeKey)
);
if (cacheVerifyCode == null) {
ResponseUtils.writeErrMsg(response, ResultCode.USER_VERIFICATION_CODE_EXPIRED);
} else {

View File

@@ -32,9 +32,9 @@ public class TokenFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
try {
if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX )) {
// 去除 Bearer 前缀
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX .length());
// 校验 JWT Token ,包括验签和是否过期
boolean isValidate = tokenManager.validateToken(token);
if (!isValidate) {

View File

@@ -8,6 +8,7 @@ import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
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;
@@ -116,7 +117,7 @@ public class JwtTokenManager implements TokenManager {
String jti = payloads.getStr(JWTPayload.JWT_ID);
// 判断是否在黑名单中如果在则返回false 标识Token无效
if (Boolean.TRUE.equals(redisTemplate.hasKey(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jti))) {
if (Boolean.TRUE.equals(redisTemplate.hasKey(RedisConstants.Auth.BLACKLIST_TOKEN + jti))) {
return false;
}
}
@@ -130,8 +131,8 @@ public class JwtTokenManager implements TokenManager {
*/
@Override
public void blacklistToken(String token) {
if (token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
if (token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX )) {
token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX .length());
}
JWT jwt = JWTUtil.parseToken(token);
JSONObject payloads = jwt.getPayloads();
@@ -146,10 +147,10 @@ public class JwtTokenManager implements TokenManager {
}
// 计算Token剩余时间将其加入黑名单
int expirationIn = expirationAt - currentTimeSeconds;
redisTemplate.opsForValue().set(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jti, null, expirationIn, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(RedisConstants.Auth.BLACKLIST_TOKEN + jti, null, expirationIn, TimeUnit.SECONDS);
} else {
// 永不过期的Token永久加入黑名单
redisTemplate.opsForValue().set(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jti, null);
redisTemplate.opsForValue().set(RedisConstants.Auth.BLACKLIST_TOKEN + jti, null);
}
;
}

View File

@@ -2,7 +2,7 @@ package com.youlai.boot.core.security.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.core.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -81,7 +81,7 @@ public class PermissionService {
Set<String> perms = new HashSet<>();
// 从缓存中一次性获取所有角色的权限
Collection<Object> roleCodesAsObjects = new ArrayList<>(roleCodes);
List<Object> rolePermsList = redisTemplate.opsForHash().multiGet(SecurityConstants.ROLE_PERMS_PREFIX, roleCodesAsObjects);
List<Object> rolePermsList = redisTemplate.opsForHash().multiGet(RedisConstants.System.ROLE_PERMS, roleCodesAsObjects);
for (Object rolePermsObj : rolePermsList) {
if (rolePermsObj instanceof Set) {

View File

@@ -121,7 +121,7 @@ public class AuthServiceImpl implements AuthService {
log.error("发送短信验证码失败", e);
}
// 缓存验证码至Redis用于登录校验
redisTemplate.opsForValue().set(RedisConstants.SMS_LOGIN_CODE_PREFIX + mobile, code, 5, TimeUnit.MINUTES);
redisTemplate.opsForValue().set(StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile), code, 5, TimeUnit.MINUTES);
}
/**
@@ -152,8 +152,8 @@ public class AuthServiceImpl implements AuthService {
@Override
public void logout() {
String token = SecurityUtils.getTokenFromRequest();
if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.BEARER_TOKEN_PREFIX )) {
token = token.substring(SecurityConstants.BEARER_TOKEN_PREFIX .length());
// 将JWT令牌加入黑名单
tokenManager.blacklistToken(token);
// 清除Security上下文
@@ -196,8 +196,12 @@ public class AuthServiceImpl implements AuthService {
// 验证码文本缓存至Redis用于登录校验
String captchaKey = IdUtil.fastSimpleUUID();
redisTemplate.opsForValue().set(SecurityConstants.CAPTCHA_CODE_PREFIX + captchaKey, captchaCode,
captchaProperties.getExpireSeconds(), TimeUnit.SECONDS);
redisTemplate.opsForValue().set(
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaKey),
captchaCode,
captchaProperties.getExpireSeconds(),
TimeUnit.SECONDS
);
return CaptchaInfo.builder()
.captchaKey(captchaKey)

View File

@@ -140,11 +140,11 @@ public class ConfigServiceImpl extends ServiceImpl<ConfigMapper, Config> impleme
*/
@Override
public boolean refreshCache() {
redisTemplate.delete(RedisConstants.SYSTEM_CONFIG_KEY);
redisTemplate.delete(RedisConstants.System.CONFIG);
List<Config> list = this.list();
if (list != null) {
Map<String, String> map = list.stream().collect(Collectors.toMap(Config::getConfigKey, Config::getConfigValue));
redisTemplate.opsForHash().putAll(RedisConstants.SYSTEM_CONFIG_KEY, map);
redisTemplate.opsForHash().putAll(RedisConstants.System.CONFIG, map);
return true;
}
return false;
@@ -159,7 +159,7 @@ public class ConfigServiceImpl extends ServiceImpl<ConfigMapper, Config> impleme
@Override
public Object getSystemConfig(String key) {
if (StringUtils.isNotBlank(key)) {
return redisTemplate.opsForHash().get(RedisConstants.SYSTEM_CONFIG_KEY, key);
return redisTemplate.opsForHash().get(RedisConstants.System.CONFIG, key);
}
return null;
}

View File

@@ -2,6 +2,7 @@ package com.youlai.boot.system.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.system.mapper.RoleMenuMapper;
import com.youlai.boot.system.model.bo.RolePermsBO;
import com.youlai.boot.system.model.entity.RoleMenu;
@@ -45,7 +46,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
@Override
public void refreshRolePermsCache() {
// 清理权限缓存
redisTemplate.opsForHash().delete(SecurityConstants.ROLE_PERMS_PREFIX, "*");
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, "*");
List<RolePermsBO> list = this.baseMapper.getRolePermsList(null);
if (CollectionUtil.isNotEmpty(list)) {
@@ -53,7 +54,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
String roleCode = item.getRoleCode();
Set<String> perms = item.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(SecurityConstants.ROLE_PERMS_PREFIX, roleCode, perms);
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, roleCode, perms);
}
});
}
@@ -65,7 +66,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
@Override
public void refreshRolePermsCache(String roleCode) {
// 清理权限缓存
redisTemplate.opsForHash().delete(SecurityConstants.ROLE_PERMS_PREFIX, roleCode);
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, roleCode);
List<RolePermsBO> list = this.baseMapper.getRolePermsList(roleCode);
if (CollectionUtil.isNotEmpty(list)) {
@@ -76,7 +77,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
Set<String> perms = rolePerms.getPerms();
if (CollectionUtil.isNotEmpty(perms)) {
redisTemplate.opsForHash().put(SecurityConstants.ROLE_PERMS_PREFIX, roleCode, perms);
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, roleCode, perms);
}
}
}
@@ -87,7 +88,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
@Override
public void refreshRolePermsCache(String oldRoleCode, String newRoleCode) {
// 清理旧角色权限缓存
redisTemplate.opsForHash().delete(SecurityConstants.ROLE_PERMS_PREFIX, oldRoleCode);
redisTemplate.opsForHash().delete(RedisConstants.System.ROLE_PERMS, oldRoleCode);
// 添加新角色权限缓存
List<RolePermsBO> list = this.baseMapper.getRolePermsList(newRoleCode);
@@ -98,7 +99,7 @@ public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> i
}
Set<String> perms = rolePerms.getPerms();
redisTemplate.opsForHash().put(SecurityConstants.ROLE_PERMS_PREFIX, newRoleCode, perms);
redisTemplate.opsForHash().put(RedisConstants.System.ROLE_PERMS, newRoleCode, perms);
}
}

View File

@@ -422,7 +422,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
boolean result = smsService.sendSms(mobile, SmsTypeEnum.CHANGE_MOBILE, templateParams);
if (result) {
// 缓存验证码5分钟有效用于更换手机号校验
String redisCacheKey = RedisConstants.SMS_CHANGE_CODE_PREFIX + mobile;
String redisCacheKey =StrUtil.format(RedisConstants.Captcha.MOBILE_CODE, mobile);
redisTemplate.opsForValue().set(redisCacheKey, code, 5, TimeUnit.MINUTES);
}
return result;
@@ -448,7 +448,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
String inputVerifyCode = form.getCode();
String mobile = form.getMobile();
String redisCacheKey = RedisConstants.SMS_CHANGE_CODE_PREFIX + mobile;
String redisCacheKey = RedisConstants.Captcha.MOBILE_CODE + mobile;
String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey);
if (StrUtil.isBlank(cachedVerifyCode)) {
@@ -482,7 +482,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
mailService.sendMail(email, "邮箱验证码", "您的验证码为:" + code + "请在5分钟内使用");
// 缓存验证码5分钟有效用于更换邮箱校验
String redisCacheKey = RedisConstants.EMAIL_CHANGE_CODE_PREFIX + email;
String redisCacheKey = StrUtil.format(RedisConstants.Captcha.EMAIL_CODE, email);
redisTemplate.opsForValue().set(redisCacheKey, code, 5, TimeUnit.MINUTES);
}
@@ -507,7 +507,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
// 获取缓存的验证码
String email = form.getEmail();
String redisCacheKey = RedisConstants.EMAIL_CHANGE_CODE_PREFIX + email;
String redisCacheKey = RedisConstants.Captcha.EMAIL_CODE + email;
String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey);
if (StrUtil.isBlank(cachedVerifyCode)) {