feat: 支持redis-token和单设备登录
This commit is contained in:
20
src/main/java/com/youlai/boot/common/enums/TokenKeyEnum.java
Normal file
20
src/main/java/com/youlai/boot/common/enums/TokenKeyEnum.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.youlai.boot.common.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Description TODO
|
||||||
|
* @Author wangtao
|
||||||
|
* @Date 2025/2/27 14:48
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum TokenKeyEnum {
|
||||||
|
ACCESS_TOKEN_KEY("access_token:"),
|
||||||
|
REFRESH_TOKEN_KEY ("refresh_token:");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
TokenKeyEnum(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@ import com.youlai.boot.core.security.exception.MyAuthenticationEntryPoint;
|
|||||||
import com.youlai.boot.core.security.extension.sms.SmsAuthenticationProvider;
|
import com.youlai.boot.core.security.extension.sms.SmsAuthenticationProvider;
|
||||||
import com.youlai.boot.core.security.extension.wechat.WechatAuthenticationProvider;
|
import com.youlai.boot.core.security.extension.wechat.WechatAuthenticationProvider;
|
||||||
import com.youlai.boot.core.security.filter.CaptchaValidationFilter;
|
import com.youlai.boot.core.security.filter.CaptchaValidationFilter;
|
||||||
import com.youlai.boot.core.security.filter.JwtAuthenticationFilter;
|
import com.youlai.boot.core.security.filter.TokenFilter;
|
||||||
|
import com.youlai.boot.core.security.manager.TokenManager;
|
||||||
import com.youlai.boot.core.security.service.SysUserDetailsService;
|
import com.youlai.boot.core.security.service.SysUserDetailsService;
|
||||||
import com.youlai.boot.core.security.manager.JwtTokenManager;
|
|
||||||
import com.youlai.boot.system.service.ConfigService;
|
import com.youlai.boot.system.service.ConfigService;
|
||||||
import com.youlai.boot.system.service.UserService;
|
import com.youlai.boot.system.service.UserService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -48,7 +48,7 @@ public class SecurityConfig {
|
|||||||
private final RedisTemplate<String, Object> redisTemplate;
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
private final JwtTokenManager jwtTokenService;
|
private final TokenManager tokenManager;
|
||||||
private final WxMaService wxMaService;
|
private final WxMaService wxMaService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final SysUserDetailsService userDetailsService;
|
private final SysUserDetailsService userDetailsService;
|
||||||
@@ -93,8 +93,8 @@ public class SecurityConfig {
|
|||||||
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(new RateLimiterFilter(redisTemplate, configService), UsernamePasswordAuthenticationFilter.class)
|
||||||
// 验证码校验过滤器
|
// 验证码校验过滤器
|
||||||
.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(new CaptchaValidationFilter(redisTemplate, codeGenerator), UsernamePasswordAuthenticationFilter.class)
|
||||||
// JWT 验证和解析过滤器
|
// 验证和解析过滤器
|
||||||
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenService), UsernamePasswordAuthenticationFilter.class)
|
.addFilterBefore(new TokenFilter(tokenManager), UsernamePasswordAuthenticationFilter.class)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ public class SecurityProperties {
|
|||||||
*/
|
*/
|
||||||
private JwtProperty jwt;
|
private JwtProperty jwt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis-Token 配置
|
||||||
|
*/
|
||||||
|
private RedisTokenProperty redisToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 白名单 URL 集合
|
* 白名单 URL 集合
|
||||||
*/
|
*/
|
||||||
@@ -62,4 +67,22 @@ public class SecurityProperties {
|
|||||||
private Integer refreshTokenTimeToLive;
|
private Integer refreshTokenTimeToLive;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class RedisTokenProperty {
|
||||||
|
/**
|
||||||
|
* 是否允许多点登录
|
||||||
|
*/
|
||||||
|
private Boolean multiLogin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问令牌有效期(单位:秒)
|
||||||
|
*/
|
||||||
|
private Integer accessTokenTimeToLive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新令牌有效期(单位:秒)
|
||||||
|
*/
|
||||||
|
private Integer refreshTokenTimeToLive;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.youlai.boot.common.constant.SecurityConstants;
|
|||||||
import com.youlai.boot.common.result.ResultCode;
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
import com.youlai.boot.common.exception.BusinessException;
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
import com.youlai.boot.common.annotation.RepeatSubmit;
|
import com.youlai.boot.common.annotation.RepeatSubmit;
|
||||||
|
import com.youlai.boot.config.property.SecurityProperties;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -37,6 +38,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class RepeatSubmitAspect {
|
public class RepeatSubmitAspect {
|
||||||
|
|
||||||
private final RedissonClient redissonClient;
|
private final RedissonClient redissonClient;
|
||||||
|
private final SecurityProperties securityProperties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防重复提交切点
|
* 防重复提交切点
|
||||||
@@ -86,17 +88,17 @@ public class RepeatSubmitAspect {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析 JWT Token 获取 jti
|
|
||||||
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
|
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);
|
String jti = (String) JWTUtil.parseToken(token).getPayload(RegisteredPayload.JWT_ID);
|
||||||
|
|
||||||
if (StrUtil.isBlank(jti)) {
|
if (StrUtil.isBlank(jti)) {
|
||||||
log.warn("JWT Token 中未找到 jti");
|
log.warn("JWT Token 中未找到 jti");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成锁的 key:前缀 + jti + 请求方法 + 请求路径
|
|
||||||
return RedisConstants.RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI();
|
|
||||||
}
|
}
|
||||||
|
// 否则会话方式为redis-token,直接使用token
|
||||||
|
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
|
||||||
|
return RedisConstants.RESUBMIT_LOCK_PREFIX + token + ":" + request.getMethod() + "-" + request.getRequestURI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.youlai.boot.common.constant.SecurityConstants;
|
import com.youlai.boot.common.constant.SecurityConstants;
|
||||||
import com.youlai.boot.common.result.ResultCode;
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
import com.youlai.boot.common.util.ResponseUtils;
|
import com.youlai.boot.common.util.ResponseUtils;
|
||||||
import com.youlai.boot.core.security.manager.JwtTokenManager;
|
import com.youlai.boot.core.security.manager.RedisTokenManager;
|
||||||
|
import com.youlai.boot.core.security.manager.TokenManager;
|
||||||
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;
|
||||||
@@ -17,27 +18,16 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT Token 验证和解析过滤器
|
* @author wangtao
|
||||||
*
|
* @since 2025/3/6 16:50
|
||||||
* @author Ray.Hao
|
|
||||||
* @since 2023/9/13
|
|
||||||
*/
|
*/
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class TokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final JwtTokenManager jwtTokenService;
|
private final TokenManager tokenManager;
|
||||||
|
|
||||||
|
public TokenFilter(TokenManager tokenManager) {
|
||||||
public JwtAuthenticationFilter(JwtTokenManager jwtTokenService) {
|
this.tokenManager = tokenManager;
|
||||||
this.jwtTokenService = jwtTokenService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从请求中获取 JWT Token,校验 JWT Token 是否合法
|
|
||||||
* <p>
|
|
||||||
* 如果合法则将 Authentication 设置到 Spring Security Context 上下文中
|
|
||||||
* 如果不合法则清空 Spring Security Context 上下文,并直接返回响应
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
|
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||||
@@ -46,13 +36,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
// 去除 Bearer 前缀
|
// 去除 Bearer 前缀
|
||||||
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
|
token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
|
||||||
// 校验 JWT Token ,包括验签和是否过期
|
// 校验 JWT Token ,包括验签和是否过期
|
||||||
boolean isValidate = jwtTokenService.validateToken(token);
|
boolean isValidate = tokenManager.validateToken(token);
|
||||||
if (!isValidate) {
|
if (!isValidate) {
|
||||||
ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
|
ResponseUtils.writeErrMsg(response, ResultCode.ACCESS_TOKEN_INVALID);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 将 Token 解析为 Authentication 对象,并设置到 Spring Security 上下文中
|
// 将 Token 解析为 Authentication 对象,并设置到 Spring Security 上下文中
|
||||||
Authentication authentication = jwtTokenService.parseToken(token);
|
Authentication authentication = tokenManager.parseToken(token);
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -1,10 +1,28 @@
|
|||||||
package com.youlai.boot.core.security.manager;
|
package com.youlai.boot.core.security.manager;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import com.youlai.boot.common.enums.TokenKeyEnum;
|
||||||
|
import com.youlai.boot.common.exception.BusinessException;
|
||||||
|
import com.youlai.boot.common.result.ResultCode;
|
||||||
|
import com.youlai.boot.config.property.SecurityProperties;
|
||||||
import com.youlai.boot.core.security.model.AuthenticationToken;
|
import com.youlai.boot.core.security.model.AuthenticationToken;
|
||||||
|
import com.youlai.boot.core.security.model.OnlineUser;
|
||||||
|
import com.youlai.boot.core.security.model.SysUserDetails;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT 令牌服务实现
|
* JWT 令牌服务实现
|
||||||
*
|
*
|
||||||
@@ -15,6 +33,14 @@ import org.springframework.stereotype.Service;
|
|||||||
@Service
|
@Service
|
||||||
public class RedisTokenManager implements TokenManager {
|
public class RedisTokenManager implements TokenManager {
|
||||||
|
|
||||||
|
private final SecurityProperties securityProperties;
|
||||||
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
public RedisTokenManager(SecurityProperties securityProperties, RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
this.securityProperties = securityProperties;
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成令牌
|
* 生成令牌
|
||||||
*
|
*
|
||||||
@@ -23,7 +49,19 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationToken generateToken(Authentication authentication) {
|
public AuthenticationToken generateToken(Authentication authentication) {
|
||||||
return null;
|
int accessTokenTimeToLive = securityProperties.getRedisToken().getAccessTokenTimeToLive();
|
||||||
|
int refreshTokenTimeToLive = securityProperties.getRedisToken().getRefreshTokenTimeToLive();
|
||||||
|
Boolean multiLogin = securityProperties.getRedisToken().getMultiLogin();
|
||||||
|
|
||||||
|
String accessToken = generateToken(authentication, TokenKeyEnum.ACCESS_TOKEN_KEY, accessTokenTimeToLive, multiLogin);
|
||||||
|
String refreshToken = generateToken(authentication, TokenKeyEnum.REFRESH_TOKEN_KEY, refreshTokenTimeToLive, multiLogin);
|
||||||
|
|
||||||
|
return AuthenticationToken.builder()
|
||||||
|
.accessToken(accessToken)
|
||||||
|
.refreshToken(refreshToken)
|
||||||
|
.tokenType("Bearer")
|
||||||
|
.expiresIn(accessTokenTimeToLive)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +72,20 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Authentication parseToken(String token) {
|
public Authentication parseToken(String token) {
|
||||||
return null;
|
String accessTokenKey = TokenKeyEnum.ACCESS_TOKEN_KEY.getValue() + token;
|
||||||
|
OnlineUser user = (OnlineUser) redisTemplate.opsForValue().get(accessTokenKey);
|
||||||
|
Set<SimpleGrantedAuthority> authorities = user.getAuthorities()
|
||||||
|
.stream()
|
||||||
|
.map(authority -> new SimpleGrantedAuthority(Convert.toStr(authority)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
SysUserDetails userDetails = new SysUserDetails();
|
||||||
|
userDetails.setUserId(user.getId());
|
||||||
|
userDetails.setUsername(user.getUsername());
|
||||||
|
userDetails.setDeptId(user.getDeptId());
|
||||||
|
userDetails.setDataScope(user.getDataScope());
|
||||||
|
userDetails.setAuthorities(authorities);
|
||||||
|
|
||||||
|
return new UsernamePasswordAuthenticationToken(userDetails, "", authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +96,9 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean validateToken(String token) {
|
public boolean validateToken(String token) {
|
||||||
return false;
|
String accessTokenKey = TokenKeyEnum.ACCESS_TOKEN_KEY.getValue() + token;
|
||||||
|
Boolean hasKey = redisTemplate.hasKey(accessTokenKey);
|
||||||
|
return hasKey != null && hasKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +109,86 @@ public class RedisTokenManager implements TokenManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public AuthenticationToken refreshToken(String token) {
|
public AuthenticationToken refreshToken(String token) {
|
||||||
return null;
|
String refreshTokenKey = TokenKeyEnum.REFRESH_TOKEN_KEY.getValue() + token;
|
||||||
|
Authentication authentication = (Authentication) redisTemplate.opsForValue().get(refreshTokenKey);
|
||||||
|
if (authentication == null) {
|
||||||
|
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
int accessTokenExpiration = securityProperties.getRedisToken().getRefreshTokenTimeToLive();
|
||||||
|
// 生成新的访问令牌
|
||||||
|
String newAccessToken = generateToken(authentication, TokenKeyEnum.ACCESS_TOKEN_KEY, accessTokenExpiration, true);
|
||||||
|
|
||||||
|
return AuthenticationToken.builder()
|
||||||
|
.accessToken(newAccessToken)
|
||||||
|
.refreshToken(token)
|
||||||
|
.expiresIn(accessTokenExpiration)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建令牌
|
||||||
|
*
|
||||||
|
* @param authentication 认证信息
|
||||||
|
* @param tokenKeyEnum 令牌类型
|
||||||
|
* @param ttl 有效期
|
||||||
|
* @param multiLogin 是否允许多点登录
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String generateToken(Authentication authentication, TokenKeyEnum tokenKeyEnum, int ttl, boolean multiLogin) {
|
||||||
|
// 获取用户信息
|
||||||
|
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
|
||||||
|
String token = UUID.randomUUID().toString();
|
||||||
|
String tokenKey = tokenKeyEnum.getValue() + token;
|
||||||
|
// 不允许多点登录,使用hashmap存储在线用户id和token
|
||||||
|
if (!multiLogin) {
|
||||||
|
// 查找当前用户id是否有token,有的话,说明已经登录了,就删除旧的token
|
||||||
|
String oldToken = (String) redisTemplate.opsForHash().get("userId-token:"+tokenKeyEnum.getValue(), userDetails.getUserId().toString());
|
||||||
|
if (StringUtils.isNotBlank(oldToken)) {
|
||||||
|
redisTemplate.opsForHash().delete("userId-token:"+tokenKeyEnum.getValue(), userDetails.getUserId().toString());
|
||||||
|
redisTemplate.delete(tokenKeyEnum.getValue() + oldToken);
|
||||||
|
}
|
||||||
|
redisTemplate.opsForHash().put("userId-token:"+tokenKeyEnum.getValue(), userDetails.getUserId().toString(), token);
|
||||||
|
// 设置userId-token的过期时间
|
||||||
|
redisTemplate.opsForHash().getOperations().expire("userId-token:"+tokenKeyEnum.getValue(), ttl, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储用户信息
|
||||||
|
OnlineUser user = new OnlineUser();
|
||||||
|
user.setId(userDetails.getUserId());
|
||||||
|
user.setUsername(userDetails.getUsername());
|
||||||
|
user.setDeptId(userDetails.getDeptId());
|
||||||
|
user.setDataScope(userDetails.getDataScope());
|
||||||
|
|
||||||
|
// claims 中添加角色信息
|
||||||
|
Set<String> roles = authentication.getAuthorities().stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
user.setAuthorities(roles);
|
||||||
|
redisTemplate.opsForValue().set(tokenKey, user, ttl, TimeUnit.SECONDS);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除redis中的用户信息
|
||||||
|
*
|
||||||
|
* @param token Redis Token
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void blacklistToken(String token) {
|
||||||
|
/**
|
||||||
|
* 根据token,删除当前用户的accessToken和refreshToken,以及 userId-token 的hashmap
|
||||||
|
*/
|
||||||
|
OnlineUser user = (OnlineUser) redisTemplate.opsForValue().get(TokenKeyEnum.ACCESS_TOKEN_KEY.getValue() + token);
|
||||||
|
if (!Objects.isNull(user)) {
|
||||||
|
Long userId = user.getId();
|
||||||
|
String refreshToken = (String) redisTemplate.opsForHash().get("userId-token:"+TokenKeyEnum.REFRESH_TOKEN_KEY.getValue(), user.getId().toString());
|
||||||
|
|
||||||
|
redisTemplate.delete(TokenKeyEnum.ACCESS_TOKEN_KEY.getValue() + token);
|
||||||
|
redisTemplate.delete(TokenKeyEnum.REFRESH_TOKEN_KEY.getValue() + refreshToken);
|
||||||
|
// 删除 userId-token 的hashmap
|
||||||
|
redisTemplate.opsForHash().delete("userId-token:"+TokenKeyEnum.ACCESS_TOKEN_KEY.getValue(), userId.toString());
|
||||||
|
redisTemplate.opsForHash().delete("userId-token:"+TokenKeyEnum.REFRESH_TOKEN_KEY.getValue(), userId.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.youlai.boot.core.security.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wangtao
|
||||||
|
* @since 2025/2/27 10:31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OnlineUser{
|
||||||
|
private Long id;
|
||||||
|
private Long deptId;
|
||||||
|
private String username;
|
||||||
|
private Integer dataScope;
|
||||||
|
private Set<String> authorities;
|
||||||
|
}
|
||||||
@@ -50,8 +50,6 @@
|
|||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "阿里云 OSS 存储桶名称"
|
"description": "阿里云 OSS 存储桶名称"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "xxl.job.enabled",
|
"name": "xxl.job.enabled",
|
||||||
"type": "java.lang.Boolean",
|
"type": "java.lang.Boolean",
|
||||||
@@ -101,7 +99,21 @@
|
|||||||
"name": "spring.cache.enabled",
|
"name": "spring.cache.enabled",
|
||||||
"type": "java.lang.Boolean",
|
"type": "java.lang.Boolean",
|
||||||
"description": "缓存开关"
|
"description": "缓存开关"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "security.redis-token.multi-login",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "是否允许多点登录"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "security.redis-token.access-token-time-to-live",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "访问令牌有效期"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "security.redis-token.refresh-token-time-to-live",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "刷新令牌有效期"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,13 @@ security:
|
|||||||
# 刷新令牌有效期(单位:秒),默认 7 天
|
# 刷新令牌有效期(单位:秒),默认 7 天
|
||||||
refresh-token-time-to-live: 604800
|
refresh-token-time-to-live: 604800
|
||||||
# 无需认证的请求路径
|
# 无需认证的请求路径
|
||||||
|
redis-token:
|
||||||
|
# 是否允许多点登录,true:允许 false:不允许
|
||||||
|
multi-login: false
|
||||||
|
# 访问令牌有效期(单位:秒),默认 1 小时
|
||||||
|
access-token-time-to-live: 3600
|
||||||
|
# 刷新令牌有效期(单位:秒),默认 7 天
|
||||||
|
refresh-token-time-to-live: 604800
|
||||||
ignore-urls:
|
ignore-urls:
|
||||||
- /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录)
|
- /api/v1/auth/login/** # 登录接口(账号密码登录、手机验证码登录和微信登录)
|
||||||
- /api/v1/auth/captcha # 验证码获取接口
|
- /api/v1/auth/captcha # 验证码获取接口
|
||||||
|
|||||||
Reference in New Issue
Block a user