mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-12 13:39:01 +08:00
refactor(user): update user information retrieval to use UserDTO and improve method documentation
This commit is contained in:
1 parent
3a53fb5677
commit
d3770993ce
54 files changed
+810
-813
No files matched your search
+5
-11
@@ -1,10 +1,7 @@
|
||||
package com.pig4cloud.pigx.auth.endpoint;
|
||||
|
||||
import com.anji.captcha.service.CaptchaCacheService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
@@ -16,27 +13,24 @@ public class CaptchaCacheServiceProvider implements CaptchaCacheService {
|
||||
|
||||
private static final String REDIS = "redis";
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Override
|
||||
public void set(String key, String value, long expiresInSeconds) {
|
||||
stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);
|
||||
RedisUtils.set(key, value, expiresInSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String key) {
|
||||
return stringRedisTemplate.hasKey(key);
|
||||
return RedisUtils.hasKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
RedisUtils.delete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String key) {
|
||||
return stringRedisTemplate.opsForValue().get(key);
|
||||
return RedisUtils.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,19 +8,17 @@ import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import io.springboot.captcha.ArithmeticCaptcha;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 验证码相关的接口
|
||||
*
|
||||
@@ -36,8 +34,6 @@ public class ImageCodeEndpoint {
|
||||
|
||||
private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
/**
|
||||
* 创建图形验证码
|
||||
*/
|
||||
@@ -53,8 +49,7 @@ public class ImageCodeEndpoint {
|
||||
return;
|
||||
}
|
||||
|
||||
redisTemplate.opsForValue()
|
||||
.set(CacheConstants.DEFAULT_CODE_KEY + randomStr, result, SecurityConstants.CODE_TIME, TimeUnit.SECONDS);
|
||||
RedisUtils.set(CacheConstants.DEFAULT_CODE_KEY + randomStr, result, SecurityConstants.CODE_TIME);
|
||||
// 转换流信息写出
|
||||
captcha.out(response.getOutputStream());
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.core.util.RetOps;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.common.security.annotation.Inner;
|
||||
import com.pig4cloud.pigx.common.security.util.OAuth2ErrorCodesExpand;
|
||||
@@ -42,7 +43,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
@@ -77,8 +77,6 @@ public class PigxTokenEndpoint {
|
||||
|
||||
private final RemoteClientDetailsService clientDetailsService;
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
private final RemoteTenantService tenantService;
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
@@ -213,7 +211,7 @@ public class PigxTokenEndpoint {
|
||||
|
||||
// 根据是否有username参数选择不同的查询key
|
||||
String searchKey = StrUtil.isNotBlank(username) ? usernameKey : key;
|
||||
Set<String> keys = redisTemplate.keys(searchKey);
|
||||
Set<String> keys = RedisUtils.keys(searchKey);
|
||||
|
||||
// 分页处理
|
||||
List<String> pages = keys.stream().skip((current - 1) * size).limit(size).toList();
|
||||
@@ -234,7 +232,7 @@ public class PigxTokenEndpoint {
|
||||
tokenVo.setId(keyParts[5]);
|
||||
tokenVo.setAccessToken(keyParts[5]);
|
||||
// 获取TTL作为过期时间
|
||||
Long ttl = redisTemplate.getExpire(keyName);
|
||||
Long ttl = RedisUtils.getExpire(keyName);
|
||||
// TTL是秒数,转换为过期时间
|
||||
long expiresAtMillis = System.currentTimeMillis() + (ttl * 1000);
|
||||
String expiresAt = TemporalAccessorUtil.format(java.time.Instant.ofEpochMilli(expiresAtMillis), DatePattern.NORM_DATETIME_PATTERN);
|
||||
|
||||
+1
-1
@@ -2,8 +2,8 @@ package com.pig4cloud.pigx.auth.support.base;
|
||||
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.pig4cloud.pigx.auth.support.store.PigxRedisOAuth2AuthorizationService;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxRedisOAuth2AuthorizationService;
|
||||
import com.pig4cloud.pigx.common.security.util.OAuth2ErrorCodesExpand;
|
||||
import com.pig4cloud.pigx.common.security.util.ScopeException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
+66
-68
@@ -30,13 +30,13 @@ import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.enums.EncFlagTypeEnum;
|
||||
import com.pig4cloud.pigx.common.core.util.RepeatBodyRequestWrapper;
|
||||
import com.pig4cloud.pigx.common.core.util.WebUtils;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
@@ -55,85 +55,83 @@ import java.util.Map;
|
||||
@RequiredArgsConstructor
|
||||
public class PasswordDecoderFilter extends OncePerRequestFilter {
|
||||
|
||||
private final AuthSecurityConfigProperties authSecurityConfigProperties;
|
||||
private static final String PASSWORD = "password";
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
static {
|
||||
// 关闭hutool 强制关闭Bouncy Castle库的依赖
|
||||
SecureUtil.disableBouncyCastle();
|
||||
}
|
||||
|
||||
private static final String PASSWORD = "password";
|
||||
private final AuthSecurityConfigProperties authSecurityConfigProperties;
|
||||
|
||||
private static final String KEY_ALGORITHM = "AES";
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
// 不是登录请求,直接向下执行
|
||||
if (!StrUtil.containsAnyIgnoreCase(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
static {
|
||||
// 关闭hutool 强制关闭Bouncy Castle库的依赖
|
||||
SecureUtil.disableBouncyCastle();
|
||||
}
|
||||
// 2. 刷新token类型,直接向下执行
|
||||
String grantType = request.getParameter("grant_type");
|
||||
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
// 不是登录请求,直接向下执行
|
||||
if (!StrUtil.containsAnyIgnoreCase(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 3. 判断客户端是否需要解密,明文传输直接向下执行
|
||||
if (!isEncClient(request)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 刷新token类型,直接向下执行
|
||||
String grantType = request.getParameter("grant_type");
|
||||
if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 将请求流转换为可多次读取的请求流
|
||||
RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);
|
||||
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
|
||||
|
||||
// 3. 判断客户端是否需要解密,明文传输直接向下执行
|
||||
if (!isEncClient(request)) {
|
||||
chain.doFilter(request, response);
|
||||
return; }
|
||||
// 构建前端对应解密AES 因子
|
||||
AES aes = new AES(Mode.CFB, Padding.NoPadding,
|
||||
new SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM),
|
||||
new IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes()));
|
||||
|
||||
// 将请求流转换为可多次读取的请求流
|
||||
RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);
|
||||
Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
|
||||
parameterMap.forEach((k, v) -> {
|
||||
String[] values = parameterMap.get(k);
|
||||
if (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建前端对应解密AES 因子
|
||||
AES aes = new AES(Mode.CFB, Padding.NoPadding,
|
||||
new SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM),
|
||||
new IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes()));
|
||||
// 解密密码
|
||||
String decryptPassword = aes.decryptStr(values[0]);
|
||||
parameterMap.put(k, new String[]{decryptPassword});
|
||||
});
|
||||
chain.doFilter(requestWrapper, response);
|
||||
}
|
||||
|
||||
parameterMap.forEach((k, v) -> {
|
||||
String[] values = parameterMap.get(k);
|
||||
if (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 根据请求的clientId 查询客户端配置是否是加密传输
|
||||
*
|
||||
* @param request 请求上下文
|
||||
* @return true 加密传输 、 false 原文传输
|
||||
*/
|
||||
private boolean isEncClient(HttpServletRequest request) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
String clientId = WebUtils.extractClientId(header).orElse(null);
|
||||
// 获取租户拼接区分租户的key
|
||||
String tenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
|
||||
CacheConstants.CLIENT_FLAG, clientId);
|
||||
|
||||
// 解密密码
|
||||
String decryptPassword = aes.decryptStr(values[0]);
|
||||
parameterMap.put(k, new String[]{decryptPassword});
|
||||
});
|
||||
chain.doFilter(requestWrapper, response);
|
||||
}
|
||||
String val = RedisUtils.get(key);
|
||||
|
||||
/**
|
||||
* 根据请求的clientId 查询客户端配置是否是加密传输
|
||||
*
|
||||
* @param request 请求上下文
|
||||
* @return true 加密传输 、 false 原文传输
|
||||
*/
|
||||
private boolean isEncClient(HttpServletRequest request) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
String clientId = WebUtils.extractClientId(header).orElse(null);
|
||||
// 获取租户拼接区分租户的key
|
||||
String tenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
|
||||
CacheConstants.CLIENT_FLAG, clientId);
|
||||
// 当配置不存在时,默认需要解密
|
||||
if (val == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String val = redisTemplate.opsForValue().get(key);
|
||||
|
||||
// 当配置不存在时,默认需要解密
|
||||
if (val == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
JSONObject information = JSONUtil.parseObj(val);
|
||||
return !StrUtil.equals(EncFlagTypeEnum.NO.getType(), information.getStr(CommonConstants.ENC_FLAG));
|
||||
}
|
||||
JSONObject information = JSONUtil.parseObj(val);
|
||||
return !StrUtil.equals(EncFlagTypeEnum.NO.getType(), information.getStr(CommonConstants.ENC_FLAG));
|
||||
}
|
||||
|
||||
}
|
||||
+105
-109
@@ -19,13 +19,13 @@ import com.pig4cloud.pigx.common.core.constant.enums.CaptchaFlagTypeEnum;
|
||||
import com.pig4cloud.pigx.common.core.exception.ValidateCodeException;
|
||||
import com.pig4cloud.pigx.common.core.util.SpringContextHolder;
|
||||
import com.pig4cloud.pigx.common.core.util.WebUtils;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
@@ -45,135 +45,131 @@ import java.io.IOException;
|
||||
@RequiredArgsConstructor
|
||||
public class ValidateCodeFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
String requestUrl = request.getServletPath();
|
||||
|
||||
// 不是登录URL 请求直接跳过
|
||||
if (!SecurityConstants.OAUTH_TOKEN_URL.equals(requestUrl)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
// 如果登录URL 但是刷新token的请求,直接向下执行
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (StrUtil.containsAny(grantType, SecurityConstants.REFRESH_TOKEN)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String requestUrl = request.getServletPath();
|
||||
// mobile模式, 如果请求不包含mobile 参数直接
|
||||
String mobile = request.getParameter(SecurityConstants.GRANT_MOBILE);
|
||||
if (StrUtil.equals(SecurityConstants.GRANT_MOBILE, grantType) && StrUtil.isBlank(mobile)) {
|
||||
throw new OAuth2AuthenticationException(SecurityConstants.GRANT_MOBILE);
|
||||
}
|
||||
|
||||
// 不是登录URL 请求直接跳过
|
||||
if (!SecurityConstants.OAUTH_TOKEN_URL.equals(requestUrl)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// mobile模式, 社交登录模式不校验验证码直接跳过
|
||||
if (StrUtil.equals(SecurityConstants.GRANT_MOBILE, grantType) && !StrUtil.contains(mobile, "SMS")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果登录URL 但是刷新token的请求,直接向下执行
|
||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||
if (StrUtil.containsAny(grantType, SecurityConstants.REFRESH_TOKEN)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
// 判断客户端是否跳过检验
|
||||
if (!isCheckCaptchaClient(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// mobile模式, 如果请求不包含mobile 参数直接
|
||||
String mobile = request.getParameter(SecurityConstants.GRANT_MOBILE);
|
||||
if (StrUtil.equals(SecurityConstants.GRANT_MOBILE, grantType) && StrUtil.isBlank(mobile)) {
|
||||
throw new OAuth2AuthenticationException(SecurityConstants.GRANT_MOBILE);
|
||||
}
|
||||
// 校验验证码 1. 客户端开启验证码 2. 短信模式
|
||||
try {
|
||||
checkCode();
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (ValidateCodeException validateCodeException) {
|
||||
throw new OAuth2AuthenticationException(validateCodeException.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// mobile模式, 社交登录模式不校验验证码直接跳过
|
||||
if (StrUtil.equals(SecurityConstants.GRANT_MOBILE, grantType) && !StrUtil.contains(mobile, "SMS")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 校验验证码
|
||||
*/
|
||||
private void checkCode() throws ValidateCodeException {
|
||||
HttpServletRequest request = WebUtils.getRequest();
|
||||
String code = request.getParameter("code");
|
||||
|
||||
// 判断客户端是否跳过检验
|
||||
if (!isCheckCaptchaClient(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(code)) {
|
||||
throw new ValidateCodeException("验证码不能为空");
|
||||
}
|
||||
|
||||
// 校验验证码 1. 客户端开启验证码 2. 短信模式
|
||||
try {
|
||||
checkCode();
|
||||
filterChain.doFilter(request, response);
|
||||
} catch (ValidateCodeException validateCodeException) {
|
||||
throw new OAuth2AuthenticationException(validateCodeException.getMessage());
|
||||
}
|
||||
}
|
||||
String randomStr = request.getParameter("randomStr");
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*/
|
||||
private void checkCode() throws ValidateCodeException {
|
||||
HttpServletRequest request = WebUtils.getRequest();
|
||||
String code = request.getParameter("code");
|
||||
// https://gitee.com/log4j/pig/issues/IWA0D
|
||||
String mobile = request.getParameter("mobile");
|
||||
if (StrUtil.isNotBlank(mobile)) {
|
||||
randomStr = mobile;
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(code)) {
|
||||
throw new ValidateCodeException("验证码不能为空");
|
||||
}
|
||||
// 若是滑块登录
|
||||
if (CommonConstants.IMAGE_CODE_TYPE.equalsIgnoreCase(randomStr)) {
|
||||
CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class);
|
||||
CaptchaVO vo = new CaptchaVO();
|
||||
vo.setCaptchaVerification(code);
|
||||
vo.setCaptchaType(CommonConstants.IMAGE_CODE_TYPE);
|
||||
if (!captchaService.verification(vo).isSuccess()) {
|
||||
throw new ValidateCodeException("验证码不能为空");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String randomStr = request.getParameter("randomStr");
|
||||
String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
|
||||
if (!RedisUtils.hasKey(key)) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
// https://gitee.com/log4j/pig/issues/IWA0D
|
||||
String mobile = request.getParameter("mobile");
|
||||
if (StrUtil.isNotBlank(mobile)) {
|
||||
randomStr = mobile;
|
||||
}
|
||||
String codeObj = RedisUtils.get(key);
|
||||
|
||||
// 若是滑块登录
|
||||
if (CommonConstants.IMAGE_CODE_TYPE.equalsIgnoreCase(randomStr)) {
|
||||
CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class);
|
||||
CaptchaVO vo = new CaptchaVO();
|
||||
vo.setCaptchaVerification(code);
|
||||
vo.setCaptchaType(CommonConstants.IMAGE_CODE_TYPE);
|
||||
if (!captchaService.verification(vo).isSuccess()) {
|
||||
throw new ValidateCodeException("验证码不能为空");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (codeObj == null) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
String saveCode = codeObj;
|
||||
if (StrUtil.isBlank(saveCode)) {
|
||||
RedisUtils.delete(key);
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
|
||||
if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
if (!StrUtil.equals(saveCode, code)) {
|
||||
RedisUtils.delete(key);
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
|
||||
Object codeObj = redisTemplate.opsForValue().get(key);
|
||||
RedisUtils.delete(key);
|
||||
}
|
||||
|
||||
if (codeObj == null) {
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
/**
|
||||
* 是否需要校验客户端,根据client 查询客户端配置
|
||||
*
|
||||
* @param request 请求
|
||||
* @return true 需要校验, false 不需要校验
|
||||
*/
|
||||
private boolean isCheckCaptchaClient(HttpServletRequest request) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
String clientId = WebUtils.extractClientId(header).orElse(null);
|
||||
// 获取租户拼接区分租户的key
|
||||
String tenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
|
||||
CacheConstants.CLIENT_FLAG, clientId);
|
||||
|
||||
String saveCode = codeObj.toString();
|
||||
if (StrUtil.isBlank(saveCode)) {
|
||||
redisTemplate.delete(key);
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
String val = RedisUtils.get(key);
|
||||
|
||||
if (!StrUtil.equals(saveCode, code)) {
|
||||
redisTemplate.delete(key);
|
||||
throw new ValidateCodeException("验证码不合法");
|
||||
}
|
||||
// 当配置不存在时,不用校验
|
||||
if (val == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
redisTemplate.delete(key);
|
||||
}
|
||||
JSONObject information = JSONUtil.parseObj(val);
|
||||
return !StrUtil.equals(CaptchaFlagTypeEnum.OFF.getType(), information.getStr(CommonConstants.CAPTCHA_FLAG));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要校验客户端,根据client 查询客户端配置
|
||||
*
|
||||
* @param request 请求
|
||||
* @return true 需要校验, false 不需要校验
|
||||
*/
|
||||
private boolean isCheckCaptchaClient(HttpServletRequest request) {
|
||||
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
String clientId = WebUtils.extractClientId(header).orElse(null);
|
||||
// 获取租户拼接区分租户的key
|
||||
String tenantId = request.getHeader(CommonConstants.TENANT_ID);
|
||||
String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
|
||||
CacheConstants.CLIENT_FLAG, clientId);
|
||||
|
||||
String val = redisTemplate.opsForValue().get(key);
|
||||
|
||||
// 当配置不存在时,不用校验
|
||||
if (val == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSONObject information = JSONUtil.parseObj(val);
|
||||
return !StrUtil.equals(CaptchaFlagTypeEnum.OFF.getType(), information.getStr(CommonConstants.CAPTCHA_FLAG));
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -25,6 +25,7 @@ import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.core.util.WebUtils;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import com.pig4cloud.pigx.common.data.resolver.ParamResolver;
|
||||
import com.pig4cloud.pigx.common.log.event.SysLogEvent;
|
||||
import com.pig4cloud.pigx.common.log.util.LogTypeEnum;
|
||||
@@ -35,7 +36,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -48,7 +49,6 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author lengleng
|
||||
@@ -61,8 +61,6 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail
|
||||
|
||||
private static final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
private final ApplicationEventPublisher publisher;
|
||||
|
||||
private final KeyStrResolver tenantKeyStrResolver;
|
||||
@@ -135,11 +133,15 @@ public class PigxAuthenticationFailureEventHandler implements AuthenticationFail
|
||||
private void recordLoginFailureTimes(String username) {
|
||||
String key = String.format("%s:%s:%s", tenantKeyStrResolver.key(), CacheConstants.LOGIN_ERROR_TIMES, username);
|
||||
Long deltaTimes = ParamResolver.getLong("LOGIN_ERROR_TIMES", 5L);
|
||||
Long times = redisTemplate.opsForValue().increment(key);
|
||||
|
||||
// 使用 RedisUtils 执行原生 Redis 命令进行递增操作
|
||||
Long times = RedisUtils.execute((RedisCallback<Long>) connection ->
|
||||
connection.incr(key.getBytes())
|
||||
);
|
||||
|
||||
// 自动过期时间
|
||||
Long deltaTime = ParamResolver.getLong("DELTA_TIME", 1L);
|
||||
redisTemplate.expire(key, deltaTime, TimeUnit.HOURS);
|
||||
RedisUtils.expire(key, deltaTime * 3600); // 转换为秒
|
||||
|
||||
if (deltaTimes <= times) {
|
||||
userService.lockUser(username);
|
||||
|
||||
+2
-4
@@ -23,6 +23,7 @@ import com.pig4cloud.pigx.common.core.constant.CacheConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.core.util.KeyStrResolver;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import com.pig4cloud.pigx.common.log.event.SysLogEvent;
|
||||
import com.pig4cloud.pigx.common.log.util.LogTypeEnum;
|
||||
import com.pig4cloud.pigx.common.log.util.SysLogUtils;
|
||||
@@ -32,7 +33,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -62,8 +62,6 @@ public class PigxAuthenticationSuccessEventHandler implements AuthenticationSucc
|
||||
|
||||
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
private final ApplicationEventPublisher publisher;
|
||||
|
||||
private final KeyStrResolver tenantKeyStrResolver;
|
||||
@@ -132,7 +130,7 @@ public class PigxAuthenticationSuccessEventHandler implements AuthenticationSucc
|
||||
private void clearLoginFailureTimes(Map<String, Object> map) {
|
||||
String key = String.format("%s:%s:%s", tenantKeyStrResolver.key(), CacheConstants.LOGIN_ERROR_TIMES,
|
||||
MapUtil.getStr(map, SecurityConstants.DETAILS_USERNAME));
|
||||
redisTemplate.delete(key);
|
||||
RedisUtils.delete(key);
|
||||
}
|
||||
|
||||
private void sendAccessTokenResponse(HttpServletResponse response, Authentication authentication)
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.pig4cloud.pigx.auth.support.store;
|
||||
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PigxRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
@Override
|
||||
public void save(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
|
||||
RedisUtils.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT * 60);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
|
||||
RedisUtils.delete(buildKey(authorizationConsent));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
|
||||
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return RedisUtils.get(buildKey(registeredClientId, principalName));
|
||||
}
|
||||
|
||||
private static String buildKey(String registeredClientId, String principalName) {
|
||||
return "token:consent:" + registeredClientId + ":" + principalName;
|
||||
}
|
||||
|
||||
private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
||||
}
|
||||
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package com.pig4cloud.pigx.auth.support.store;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
|
||||
import com.pig4cloud.pigx.common.data.cache.RedisUtils;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于Redis实现的OAuth2授权服务类,提供授权信息的存储、查询和删除功能
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2025/06/30
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PigxRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
|
||||
|
||||
private final static Long TIMEOUT = 10L;
|
||||
|
||||
private static final String AUTHORIZATION = "token";
|
||||
|
||||
private static boolean isState(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAttribute("state"));
|
||||
}
|
||||
|
||||
private static boolean isCode(OAuth2Authorization authorization) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
return Objects.nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
private static boolean isRefreshToken(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getRefreshToken());
|
||||
}
|
||||
|
||||
private static boolean isAccessToken(OAuth2Authorization authorization) {
|
||||
return Objects.nonNull(authorization.getAccessToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT * 60);
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
|
||||
authorizationCodeToken.getExpiresAt());
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization,
|
||||
(int) (between * 60));
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, (int) between);
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
|
||||
RedisUtils.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, (int) between);
|
||||
|
||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = authorization.getAttribute(Principal.class.getName());
|
||||
|
||||
if (Objects.nonNull(usernamePasswordAuthenticationToken) && usernamePasswordAuthenticationToken.getPrincipal() instanceof PigxUser pigxUser) {
|
||||
// 扩展记录 access-token 、username 的关系 token::username::admin::tenantId::xxx
|
||||
String tokenUsername = String.format("%s::%s::%s::%s::%s::%s", AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), pigxUser.getTenantId(), accessToken.getTokenValue());
|
||||
RedisUtils.set(tokenUsername, accessToken.getTokenValue(), (int) between);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(OAuth2Authorization authorization) {
|
||||
Assert.notNull(authorization, "authorization cannot be null");
|
||||
|
||||
List<String> keys = new ArrayList<>();
|
||||
if (isState(authorization)) {
|
||||
String token = authorization.getAttribute("state");
|
||||
keys.add(buildKey(OAuth2ParameterNames.STATE, token));
|
||||
}
|
||||
|
||||
if (isCode(authorization)) {
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = authorization
|
||||
.getToken(OAuth2AuthorizationCode.class);
|
||||
OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));
|
||||
}
|
||||
|
||||
if (isRefreshToken(authorization)) {
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()));
|
||||
}
|
||||
|
||||
if (isAccessToken(authorization)) {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String key = String.format("%s::%s::%s::%s::*::%s", AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authorization.getPrincipalName(), authorization.getRegisteredClientId(), accessToken.getTokenValue());
|
||||
keys.add(key);
|
||||
}
|
||||
|
||||
RedisUtils.delete(keys.toArray(new String[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2Authorization findById(String id) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
|
||||
Assert.hasText(token, "token cannot be empty");
|
||||
return RedisUtils.get(buildKey(Objects.requireNonNullElse(tokenType, OAuth2TokenType.ACCESS_TOKEN).getValue(), token));
|
||||
}
|
||||
|
||||
private String buildKey(String type, String id) {
|
||||
return String.format("%s::%s::%s", AUTHORIZATION, type, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名移除相关授权信息
|
||||
*
|
||||
* @param authentication 认证信息,包含用户名
|
||||
*/
|
||||
public void removeByUsername(Authentication authentication) {
|
||||
// 根据 username查询对应access-token
|
||||
String authenticationName = authentication.getName();
|
||||
|
||||
// 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
|
||||
String tokenUsernameKey = String.format("%s::%s::%s::*", AUTHORIZATION,
|
||||
SecurityConstants.DETAILS_USERNAME, authenticationName);
|
||||
Set<String> keys = RedisUtils.keys(tokenUsernameKey);
|
||||
if (CollUtil.isEmpty(keys)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> tokenList = RedisUtils.multiGet(keys.stream().toList());
|
||||
|
||||
for (String token : tokenList) {
|
||||
// 根据token 查询存储的 OAuth2Authorization
|
||||
OAuth2Authorization authorization = this.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
|
||||
// 根据 OAuth2Authorization 删除相关令牌
|
||||
this.remove(authorization);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user