refactor: 会话失效、数据权限和实时推送重构
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package com.youlai.boot.security.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色数据权限信息
|
||||
* <p>
|
||||
* 用于存储单个角色的数据权限范围信息,支持多角色数据权限合并(并集策略)
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleDataScope implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String roleCode;
|
||||
|
||||
/**
|
||||
* 数据权限范围值
|
||||
* 1-所有数据 2-部门及子部门数据 3-本部门数据 4-本人数据 5-自定义部门数据
|
||||
*/
|
||||
private Integer dataScope;
|
||||
|
||||
/**
|
||||
* 自定义部门ID列表(仅当 dataScope=5 时有效)
|
||||
*/
|
||||
private List<Long> customDeptIds;
|
||||
|
||||
/**
|
||||
* 创建"全部数据"权限
|
||||
*/
|
||||
public static RoleDataScope all(String roleCode) {
|
||||
return new RoleDataScope(roleCode, 1, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建"部门及子部门"权限
|
||||
*/
|
||||
public static RoleDataScope deptAndSub(String roleCode) {
|
||||
return new RoleDataScope(roleCode, 2, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建"本部门"权限
|
||||
*/
|
||||
public static RoleDataScope dept(String roleCode) {
|
||||
return new RoleDataScope(roleCode, 3, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建"本人"权限
|
||||
*/
|
||||
public static RoleDataScope self(String roleCode) {
|
||||
return new RoleDataScope(roleCode, 4, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建"自定义部门"权限
|
||||
*/
|
||||
public static RoleDataScope custom(String roleCode, List<Long> deptIds) {
|
||||
return new RoleDataScope(roleCode, 5, deptIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,59 +8,71 @@ import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 短信验证码认证 Token
|
||||
* <p>
|
||||
* 用于短信验证码登录场景,遵循 Spring Security 认证模型:
|
||||
* <ul>
|
||||
* <li>未认证状态:principal 为手机号,credentials 为验证码</li>
|
||||
* <li>已认证状态:principal 为用户详情,credentials 为 null</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.20.0
|
||||
*/
|
||||
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 621L;
|
||||
|
||||
/**
|
||||
* 认证信息 (手机号)
|
||||
* 认证信息
|
||||
* <ul>
|
||||
* <li>未认证时:手机号</li>
|
||||
* <li>已认证时:SysUserDetails 用户详情</li>
|
||||
* </ul>
|
||||
*/
|
||||
private final Object principal;
|
||||
|
||||
/**
|
||||
* 凭证信息 (短信验证码)
|
||||
* 凭证信息
|
||||
* <ul>
|
||||
* <li>未认证时:短信验证码</li>
|
||||
* <li>已认证时:null</li>
|
||||
* </ul>
|
||||
*/
|
||||
private final Object credentials;
|
||||
|
||||
/**
|
||||
* 短信验证码认证 Token (未认证)
|
||||
* 创建未认证的 Token
|
||||
*
|
||||
* @param principal 微信用户信息
|
||||
* @param mobile 手机号
|
||||
* @param verifyCode 短信验证码
|
||||
*/
|
||||
public SmsAuthenticationToken(Object principal, Object credentials) {
|
||||
// 没有授权信息时,设置为 null
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
// 默认未认证
|
||||
this.setAuthenticated(false);
|
||||
public SmsAuthenticationToken(String mobile, String verifyCode) {
|
||||
super(null);
|
||||
this.principal = mobile;
|
||||
this.credentials = verifyCode;
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信验证码认证 Token (已认证)
|
||||
* 创建已认证的 Token
|
||||
*
|
||||
* @param principal 用户信息
|
||||
* @param principal 用户详情(SysUserDetails)
|
||||
* @param authorities 授权信息
|
||||
*/
|
||||
public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
this.credentials = null;
|
||||
// 认证通过
|
||||
super.setAuthenticated(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 认证通过
|
||||
* 创建已认证的 Token(静态工厂方法)
|
||||
*
|
||||
* @param principal 用户信息
|
||||
* @param principal 用户详情(SysUserDetails)
|
||||
* @param authorities 授权信息
|
||||
* @return SmsAuthenticationToken
|
||||
* @return 已认证的 SmsAuthenticationToken
|
||||
*/
|
||||
public static SmsAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
|
||||
return new SmsAuthenticationToken(principal, authorities);
|
||||
|
||||
@@ -3,15 +3,13 @@ package com.youlai.boot.security.model;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.youlai.boot.common.constant.SecurityConstants;
|
||||
import com.youlai.boot.security.model.UserAuthInfo;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -53,9 +51,11 @@ public class SysUserDetails implements UserDetails {
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 数据权限范围
|
||||
* 数据权限列表
|
||||
* <p>
|
||||
* 存储用户所有角色的数据权限范围,用于实现多角色权限合并(并集策略)
|
||||
*/
|
||||
private Integer dataScope;
|
||||
private List<RoleDataScope> dataScopes;
|
||||
|
||||
/**
|
||||
* 用户角色权限集合
|
||||
@@ -73,7 +73,7 @@ public class SysUserDetails implements UserDetails {
|
||||
this.password = user.getPassword();
|
||||
this.enabled = ObjectUtil.equal(user.getStatus(), 1);
|
||||
this.deptId = user.getDeptId();
|
||||
this.dataScope = user.getDataScope();
|
||||
this.dataScopes = user.getDataScopes();
|
||||
|
||||
// 初始化角色权限集合
|
||||
this.authorities = CollectionUtil.isNotEmpty(user.getRoles())
|
||||
@@ -104,4 +104,26 @@ public class SysUserDetails implements UserDetails {
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否包含"全部数据"权限
|
||||
*
|
||||
* @return 是否有全部数据权限
|
||||
*/
|
||||
public boolean hasAllDataScope() {
|
||||
if (CollectionUtil.isEmpty(dataScopes)) {
|
||||
return false;
|
||||
}
|
||||
return dataScopes.stream()
|
||||
.anyMatch(scope -> scope.getDataScope() == 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限列表
|
||||
*
|
||||
* @return 数据权限列表,永不为null
|
||||
*/
|
||||
public List<RoleDataScope> getDataScopes() {
|
||||
return dataScopes != null ? dataScopes : Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.youlai.boot.security.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -52,7 +53,9 @@ public class UserAuthInfo {
|
||||
private Set<String> roles;
|
||||
|
||||
/**
|
||||
* 数据权限范围
|
||||
* 数据权限列表
|
||||
* <p>
|
||||
* 存储用户所有角色的数据权限范围,用于实现多角色权限合并(并集策略)
|
||||
*/
|
||||
private Integer dataScope;
|
||||
private List<RoleDataScope> dataScopes;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,14 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 在线用户信息对象
|
||||
* 用户会话信息
|
||||
* <p>
|
||||
* 存储在Token中的用户会话快照,包含用户身份、数据权限和角色权限信息。
|
||||
* 用于Redis-Token模式下的会话管理,支持在线用户查询和会话控制。
|
||||
*
|
||||
* @author wangtao
|
||||
* @since 2025/2/27 10:31
|
||||
@@ -15,7 +19,7 @@ import java.util.Set;
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OnlineUser {
|
||||
public class UserSession {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
@@ -33,10 +37,9 @@ public class OnlineUser {
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 数据权限范围
|
||||
* <p>定义用户可访问的数据范围,如全部、本部门或自定义范围</p>
|
||||
* 数据权限列表
|
||||
*/
|
||||
private Integer dataScope;
|
||||
private List<RoleDataScope> dataScopes;
|
||||
|
||||
/**
|
||||
* 角色权限集合
|
||||
@@ -18,9 +18,22 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
|
||||
/**
|
||||
* 短信验证码认证 Provider
|
||||
* <p>
|
||||
* 实现 Spring Security 的 {@link AuthenticationProvider} 接口,处理短信验证码登录认证。
|
||||
* <p>
|
||||
* 认证流程:
|
||||
* <ol>
|
||||
* <li>根据手机号查询用户信息</li>
|
||||
* <li>校验用户状态(是否禁用)</li>
|
||||
* <li>校验短信验证码(与 Redis 缓存比对)</li>
|
||||
* <li>验证成功后删除验证码,防止重复使用</li>
|
||||
* <li>返回已认证的 Authentication</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2.17.0
|
||||
* @see SmsAuthenticationToken
|
||||
* @see AuthenticationProvider
|
||||
*/
|
||||
@Slf4j
|
||||
public class SmsAuthenticationProvider implements AuthenticationProvider {
|
||||
@@ -29,58 +42,79 @@ public class SmsAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
|
||||
public SmsAuthenticationProvider(UserService userService, RedisTemplate<String, Object> redisTemplate) {
|
||||
this.userService = userService;
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信验证码认证逻辑,参考 Spring Security 认证密码校验流程
|
||||
* 执行短信验证码认证
|
||||
*
|
||||
* @param authentication 认证对象
|
||||
* @return 认证后的 Authentication 对象
|
||||
* @throws AuthenticationException 认证异常
|
||||
* @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authenticate(Authentication)
|
||||
* @param authentication 未认证的 {@link SmsAuthenticationToken}
|
||||
* @return 已认证的 {@link SmsAuthenticationToken}
|
||||
* @throws AuthenticationException 认证失败异常
|
||||
*/
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
String mobile = (String) authentication.getPrincipal();
|
||||
String inputVerifyCode = (String) authentication.getCredentials();
|
||||
|
||||
// 参数校验
|
||||
if (StrUtil.isBlank(mobile)) {
|
||||
log.warn("短信验证码登录失败:手机号为空");
|
||||
throw new CaptchaValidationException("手机号不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(inputVerifyCode)) {
|
||||
log.warn("短信验证码登录失败:验证码为空,手机号={}", mobile);
|
||||
throw new CaptchaValidationException("验证码不能为空");
|
||||
}
|
||||
|
||||
// 根据手机号获取用户信息
|
||||
UserAuthInfo userAuthInfo = userService.getAuthInfoByMobile(mobile);
|
||||
|
||||
if (userAuthInfo == null) {
|
||||
log.warn("短信验证码登录失败:用户不存在,手机号={}", mobile);
|
||||
throw new UsernameNotFoundException("用户不存在");
|
||||
}
|
||||
|
||||
// 检查用户状态是否有效
|
||||
if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) {
|
||||
log.warn("短信验证码登录失败:用户已禁用,用户名={}", userAuthInfo.getUsername());
|
||||
throw new DisabledException("用户已被禁用");
|
||||
}
|
||||
|
||||
// 校验发送短信验证码的手机号是否与当前登录用户一致
|
||||
// 校验短信验证码
|
||||
String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile);
|
||||
String cachedVerifyCode = (String) redisTemplate.opsForValue().get(cacheKey);
|
||||
|
||||
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
|
||||
throw new CaptchaValidationException("验证码错误");
|
||||
} else {
|
||||
// 验证成功后删除验证码
|
||||
redisTemplate.delete(cacheKey);
|
||||
if (cachedVerifyCode == null) {
|
||||
log.warn("短信验证码登录失败:验证码已过期,手机号={}", mobile);
|
||||
throw new CaptchaValidationException("验证码已过期,请重新获取");
|
||||
}
|
||||
|
||||
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
|
||||
log.warn("短信验证码登录失败:验证码错误,手机号={}", mobile);
|
||||
throw new CaptchaValidationException("验证码错误");
|
||||
}
|
||||
|
||||
// 验证成功后删除验证码,防止重复使用
|
||||
redisTemplate.delete(cacheKey);
|
||||
|
||||
// 构建认证后的用户详情信息
|
||||
SysUserDetails userDetails = new SysUserDetails(userAuthInfo);
|
||||
|
||||
log.info("短信验证码登录成功:用户名={},手机号={}", userAuthInfo.getUsername(), mobile);
|
||||
|
||||
// 创建已认证的 SmsAuthenticationToken
|
||||
return SmsAuthenticationToken.authenticated(
|
||||
userDetails,
|
||||
userDetails.getAuthorities()
|
||||
);
|
||||
return SmsAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities());
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持的认证类型
|
||||
*
|
||||
* @param authentication 认证类型
|
||||
* @return 是否支持该认证类型
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
|
||||
@@ -2,18 +2,21 @@ package com.youlai.boot.security.service;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.youlai.boot.common.constant.RedisConstants;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.system.service.RoleMenuService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* SpringSecurity 权限校验
|
||||
* Spring Security 权限校验组件
|
||||
* <p>
|
||||
* 用于 SpEL 表达式权限校验,如:@PreAuthorize("@ss.hasPerm('sys:user:add')")
|
||||
* <p>
|
||||
* 权限数据来源:{@link RoleMenuService#getRolePermsByRoleCodes}(带 Redis 缓存)
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 0.0.1
|
||||
@@ -23,19 +26,21 @@ import java.util.*;
|
||||
@Slf4j
|
||||
public class PermissionService {
|
||||
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final RoleMenuService roleMenuService;
|
||||
|
||||
/**
|
||||
* 判断当前登录用户是否拥有操作权限
|
||||
* <p>
|
||||
* 支持通配符匹配,如:权限码 "sys:user:*" 可匹配 "sys:user:add"、"sys:user:delete" 等
|
||||
*
|
||||
* @param requiredPerm 所需权限
|
||||
* @return 是否有权限
|
||||
*/
|
||||
public boolean hasPerm(String requiredPerm) {
|
||||
|
||||
if (StrUtil.isBlank(requiredPerm)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 超级管理员放行
|
||||
if (SecurityUtils.isRoot()) {
|
||||
return true;
|
||||
@@ -47,52 +52,21 @@ public class PermissionService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前登录用户的所有角色的权限列表
|
||||
Set<String> rolePerms = this.getRolePermsFormCache(roleCodes);
|
||||
// 获取当前登录用户的所有角色的权限列表(从缓存读取)
|
||||
Set<String> rolePerms = roleMenuService.getRolePermsByRoleCodes(roleCodes);
|
||||
if (CollectionUtil.isEmpty(rolePerms)) {
|
||||
return false;
|
||||
}
|
||||
// 判断当前登录用户的所有角色的权限列表中是否包含所需权限
|
||||
|
||||
// 判断权限列表中是否包含所需权限(支持通配符)
|
||||
boolean hasPermission = rolePerms.stream()
|
||||
.anyMatch(rolePerm ->
|
||||
// 匹配权限,支持通配符(* 等)
|
||||
PatternMatchUtils.simpleMatch(rolePerm, requiredPerm)
|
||||
);
|
||||
.anyMatch(rolePerm -> PatternMatchUtils.simpleMatch(rolePerm, requiredPerm));
|
||||
|
||||
if (!hasPermission) {
|
||||
log.error("用户无操作权限:{}",requiredPerm);
|
||||
log.warn("用户无操作权限:userId={}, username={}, requiredPerm={}",
|
||||
SecurityUtils.getUserId(), SecurityUtils.getUsername(), requiredPerm);
|
||||
}
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从缓存中获取角色权限列表
|
||||
*
|
||||
* @param roleCodes 角色编码集合
|
||||
* @return 角色权限列表
|
||||
*/
|
||||
public Set<String> getRolePermsFormCache(Set<String> roleCodes) {
|
||||
if (CollectionUtil.isEmpty(roleCodes)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// 构建缓存Key
|
||||
String cacheKey = RedisConstants.System.ROLE_PERMS;
|
||||
|
||||
Set<String> perms = new HashSet<>();
|
||||
Collection<Object> roleCodesAsObjects = new ArrayList<>(roleCodes);
|
||||
List<Object> rolePermsList = redisTemplate.opsForHash().multiGet(cacheKey, roleCodesAsObjects);
|
||||
|
||||
for (Object rolePermsObj : rolePermsList) {
|
||||
if (rolePermsObj instanceof Set) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> rolePerms = (Set<String>) rolePermsObj;
|
||||
perms.addAll(rolePerms);
|
||||
}
|
||||
}
|
||||
|
||||
return perms;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTPayload;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
@@ -15,6 +17,7 @@ import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.config.property.SecurityProperties;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.RoleDataScope;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -25,17 +28,20 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT Token 管理器
|
||||
* <p>
|
||||
* 用于生成、解析、校验、刷新 JWT Token
|
||||
* 实现基于JWT的无状态认证,支持:
|
||||
* <ul>
|
||||
* <li>Access Token + Refresh Token 双令牌机制</li>
|
||||
* <li>Token 撤销(jti黑名单)</li>
|
||||
* <li>用户级会话失效(tokenValidAfter)</li>
|
||||
* <li>多角色数据权限存储</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2024/11/15
|
||||
@@ -44,6 +50,9 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class JwtTokenManager implements TokenManager {
|
||||
|
||||
/** tokenValidAfter 默认过期时间(7天),避免Redis内存泄漏 */
|
||||
private static final long TOKEN_VALID_AFTER_TTL_SECONDS = 7 * 24 * 60 * 60;
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
private final byte[] secretKey;
|
||||
@@ -90,7 +99,25 @@ public class JwtTokenManager implements TokenManager {
|
||||
SysUserDetails userDetails = new SysUserDetails();
|
||||
userDetails.setUserId(payloads.getLong(JwtClaimConstants.USER_ID)); // 用户ID
|
||||
userDetails.setDeptId(payloads.getLong(JwtClaimConstants.DEPT_ID)); // 部门ID
|
||||
userDetails.setDataScope(payloads.getInt(JwtClaimConstants.DATA_SCOPE)); // 数据权限范围
|
||||
|
||||
// 解析数据权限列表
|
||||
JSONArray dataScopesArray = payloads.getJSONArray(JwtClaimConstants.DATA_SCOPES);
|
||||
if (dataScopesArray != null && !dataScopesArray.isEmpty()) {
|
||||
List<RoleDataScope> dataScopes = dataScopesArray.stream()
|
||||
.map(obj -> {
|
||||
JSONObject item = (JSONObject) obj;
|
||||
String roleCode = item.getStr("roleCode");
|
||||
Integer dataScope = item.getInt("dataScope");
|
||||
JSONArray deptIdsArray = item.getJSONArray("customDeptIds");
|
||||
List<Long> customDeptIds = null;
|
||||
if (deptIdsArray != null) {
|
||||
customDeptIds = deptIdsArray.toList(Long.class);
|
||||
}
|
||||
return new RoleDataScope(roleCode, dataScope, customDeptIds);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
userDetails.setDataScopes(dataScopes);
|
||||
}
|
||||
|
||||
userDetails.setUsername(payloads.getStr(JWTPayload.SUBJECT)); // 用户名
|
||||
// 角色集合
|
||||
@@ -126,9 +153,17 @@ public class JwtTokenManager implements TokenManager {
|
||||
|
||||
/**
|
||||
* 校验令牌
|
||||
* <p>
|
||||
* 校验流程(按顺序执行):
|
||||
* <ol>
|
||||
* <li>签名验证 + 过期时间检查</li>
|
||||
* <li>刷新令牌类型校验(仅刷新场景)</li>
|
||||
* <li>tokenValidAfter 校验(用户级会话失效)</li>
|
||||
* <li>jti 黑名单校验(单Token撤销)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @param validateRefreshToken 是否校验刷新令牌
|
||||
* @param validateRefreshToken 是否校验刷新令牌类型
|
||||
* @return 是否有效
|
||||
*/
|
||||
private boolean validateToken(String token, boolean validateRefreshToken) {
|
||||
@@ -147,27 +182,28 @@ public class JwtTokenManager implements TokenManager {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 2. 校验安全版本号(用于按用户维度失效历史 Token)
|
||||
// 场景示例:用户修改密码、被管理员强制下线、手动“踢所有端”后,将用户安全版本号 +1,旧版本 Token 全部失效
|
||||
// 2. 校验 tokenValidAfter(用于按用户维度失效历史 Token)
|
||||
// 场景示例:用户修改密码、被管理员强制下线、手动“踢所有端”后,更新 tokenValidAfter,早于该时间签发的 Token 全部失效
|
||||
Long userId = payloads.getLong(JwtClaimConstants.USER_ID);
|
||||
if (userId != null) {
|
||||
// 老版本 Token 可能没有 SECURITY_VERSION 声明,视为 0 版本
|
||||
Integer tokenVersionRaw = payloads.getInt(JwtClaimConstants.SECURITY_VERSION);
|
||||
int tokenVersion = tokenVersionRaw != null ? tokenVersionRaw : 0;
|
||||
Object issuedAtObj = payloads.get(JWTPayload.ISSUED_AT);
|
||||
long issuedAtSeconds = 0;
|
||||
if (issuedAtObj instanceof Date issuedAtDate) {
|
||||
issuedAtSeconds = issuedAtDate.getTime() / 1000;
|
||||
}
|
||||
|
||||
String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userId);
|
||||
Integer currentVersionRaw = (Integer) redisTemplate.opsForValue().get(versionKey);
|
||||
int currentVersion = currentVersionRaw != null ? currentVersionRaw : 0;
|
||||
String validAfterKey = StrUtil.format(RedisConstants.Auth.USER_TOKEN_VALID_AFTER, userId);
|
||||
Object validAfterObj = redisTemplate.opsForValue().get(validAfterKey);
|
||||
long validAfterSeconds = validAfterObj != null ? Convert.toLong(validAfterObj) : 0;
|
||||
|
||||
// 如果当前版本号比 Token 携带的版本号新,则认为该 Token 已失效
|
||||
if (tokenVersion < currentVersion) {
|
||||
if (issuedAtSeconds < validAfterSeconds) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 判断是否在黑名单中,如果在,则返回 false 标识Token无效
|
||||
// 3. 判断 Token 是否已被撤销(单端退出/会话注销)
|
||||
// 场景示例:单点退出登录、后台手动注销某个会话、封禁账号后立即阻断当前 Token 等
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, jti)))) {
|
||||
if (isTokenRevoked(jti)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -190,29 +226,63 @@ public class JwtTokenManager implements TokenManager {
|
||||
}
|
||||
JWT jwt = JWTUtil.parseToken(token);
|
||||
JSONObject payloads = jwt.getPayloads();
|
||||
String jti = payloads.getStr(JWTPayload.JWT_ID);
|
||||
Integer expirationAt = payloads.getInt(JWTPayload.EXPIRES_AT);
|
||||
// 黑名单Token Key
|
||||
String blacklistTokenKey = StrUtil.format(RedisConstants.Auth.BLACKLIST_TOKEN, payloads.getStr(JWTPayload.JWT_ID));
|
||||
revokeTokenByJti(jti, expirationAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Token是否已被撤销
|
||||
*
|
||||
* @param jti Token唯一标识
|
||||
* @return true-已撤销,false-未撤销
|
||||
*/
|
||||
private boolean isTokenRevoked(String jti) {
|
||||
if (StringUtils.isBlank(jti)) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.TRUE.equals(redisTemplate.hasKey(StrUtil.format(RedisConstants.Auth.REVOKED_JTI, jti)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Token加入撤销黑名单
|
||||
* <p>
|
||||
* 黑名单有效期与Token剩余有效期一致,避免永久存储
|
||||
*
|
||||
* @param jti Token唯一标识
|
||||
* @param expirationAt Token过期时间戳
|
||||
*/
|
||||
private void revokeTokenByJti(String jti, Integer expirationAt) {
|
||||
if (StringUtils.isBlank(jti)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String revokedJtiKey = StrUtil.format(RedisConstants.Auth.REVOKED_JTI, jti);
|
||||
if (expirationAt != null) {
|
||||
int currentTimeSeconds = Convert.toInt(System.currentTimeMillis() / 1000);
|
||||
if (expirationAt < currentTimeSeconds) {
|
||||
// Token已过期,直接返回
|
||||
return;
|
||||
}
|
||||
// 计算Token剩余时间,将其加入黑名单(值使用简单布尔标记即可)
|
||||
int expirationIn = expirationAt - currentTimeSeconds;
|
||||
redisTemplate.opsForValue().set(blacklistTokenKey, Boolean.TRUE, expirationIn, TimeUnit.SECONDS);
|
||||
redisTemplate.opsForValue().set(revokedJtiKey, Boolean.TRUE, expirationIn, TimeUnit.SECONDS);
|
||||
} else {
|
||||
// 永不过期的Token永久加入黑名单
|
||||
redisTemplate.opsForValue().set(blacklistTokenKey, Boolean.TRUE);
|
||||
redisTemplate.opsForValue().set(revokedJtiKey, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 失效指定用户的所有会话
|
||||
* <p>
|
||||
* 通过提升用户的安全版本号,使携带旧版本号的 Token 在后续校验时全部失效
|
||||
* 通过更新用户 tokenValidAfter 时间戳,使早于该时间签发的 Token 全部失效。
|
||||
* <p>
|
||||
* 适用场景:
|
||||
* <ul>
|
||||
* <li>用户修改密码</li>
|
||||
* <li>管理员强制下线用户</li>
|
||||
* <li>用户主动踢出所有设备</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
@Override
|
||||
public void invalidateUserSessions(Long userId) {
|
||||
@@ -220,10 +290,10 @@ public class JwtTokenManager implements TokenManager {
|
||||
return;
|
||||
}
|
||||
|
||||
String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userId);
|
||||
// 递增版本号
|
||||
redisTemplate.opsForValue().increment(versionKey);
|
||||
|
||||
String validAfterKey = StrUtil.format(RedisConstants.Auth.USER_TOKEN_VALID_AFTER, userId);
|
||||
long nowSeconds = System.currentTimeMillis() / 1000;
|
||||
// 设置过期时间,避免Redis内存泄漏
|
||||
redisTemplate.opsForValue().set(validAfterKey, nowSeconds, TOKEN_VALID_AFTER_TTL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,8 +323,8 @@ public class JwtTokenManager implements TokenManager {
|
||||
* 生成 JWT Token
|
||||
*
|
||||
* @param authentication 认证信息
|
||||
* @param ttl 过期时间
|
||||
* @return JWT Token
|
||||
* @param ttl 过期时间(秒),-1表示永不过期
|
||||
* @return JWT Token字符串
|
||||
*/
|
||||
private String generateToken(Authentication authentication, int ttl) {
|
||||
return generateToken(authentication, ttl, false);
|
||||
@@ -263,18 +333,43 @@ public class JwtTokenManager implements TokenManager {
|
||||
|
||||
/**
|
||||
* 生成 JWT Token
|
||||
* <p>
|
||||
* Payload包含:
|
||||
* <ul>
|
||||
* <li>userId - 用户ID</li>
|
||||
* <li>deptId - 部门ID</li>
|
||||
* <li>dataScopes - 数据权限列表</li>
|
||||
* <li>authorities - 角色权限集合</li>
|
||||
* <li>tokenType - 是否为刷新令牌</li>
|
||||
* <li>iat/exp - 签发/过期时间</li>
|
||||
* <li>jti - Token唯一标识(用于撤销)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param authentication 认证信息
|
||||
* @param ttl 过期时间
|
||||
* @param isRefreshToken 类型是否为刷新token
|
||||
* @return JWT Token
|
||||
* @param authentication 认证信息
|
||||
* @param ttl 过期时间(秒)
|
||||
* @param isRefreshToken 是否为刷新令牌
|
||||
* @return JWT Token字符串
|
||||
*/
|
||||
private String generateToken(Authentication authentication, int ttl, boolean isRefreshToken) {
|
||||
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID
|
||||
payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID
|
||||
payload.put(JwtClaimConstants.DATA_SCOPE, userDetails.getDataScope()); // 数据权限范围
|
||||
|
||||
// 存储数据权限列表
|
||||
List<RoleDataScope> dataScopes = userDetails.getDataScopes();
|
||||
if (dataScopes != null && !dataScopes.isEmpty()) {
|
||||
List<Map<String, Object>> scopesList = dataScopes.stream()
|
||||
.map(scope -> {
|
||||
Map<String, Object> scopeMap = new HashMap<>();
|
||||
scopeMap.put("roleCode", scope.getRoleCode());
|
||||
scopeMap.put("dataScope", scope.getDataScope());
|
||||
scopeMap.put("customDeptIds", scope.getCustomDeptIds());
|
||||
return scopeMap;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
payload.put(JwtClaimConstants.DATA_SCOPES, scopesList);
|
||||
}
|
||||
|
||||
// claims 中添加角色信息
|
||||
Set<String> roles = authentication.getAuthorities().stream()
|
||||
@@ -289,12 +384,6 @@ public class JwtTokenManager implements TokenManager {
|
||||
payload.put(JwtClaimConstants.TOKEN_TYPE, true);
|
||||
}
|
||||
|
||||
// 设置安全版本号:不存在时默认为 0
|
||||
String versionKey = StrUtil.format(RedisConstants.Auth.USER_SECURITY_VERSION, userDetails.getUserId());
|
||||
Integer currentVersion = (Integer) redisTemplate.opsForValue().get(versionKey);
|
||||
int securityVersion = currentVersion != null ? currentVersion : 0;
|
||||
payload.put(JwtClaimConstants.SECURITY_VERSION, securityVersion);
|
||||
|
||||
// 设置过期时间 -1 表示永不过期
|
||||
if (ttl != -1) {
|
||||
Date expiresAt = DateUtil.offsetSecond(now, ttl);
|
||||
|
||||
@@ -8,7 +8,8 @@ import com.youlai.boot.core.exception.BusinessException;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.config.property.SecurityProperties;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.security.model.OnlineUser;
|
||||
import com.youlai.boot.security.model.UserSession;
|
||||
import com.youlai.boot.security.model.RoleDataScope;
|
||||
import com.youlai.boot.security.model.SysUserDetails;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -26,7 +27,15 @@ import java.util.stream.Collectors;
|
||||
/**
|
||||
* Redis Token 管理器
|
||||
* <p>
|
||||
* 用于生成、解析、校验、刷新 Redis Token
|
||||
* 实现基于Redis的有状态认证,支持:
|
||||
* <ul>
|
||||
* <li>Access Token + Refresh Token 双令牌机制</li>
|
||||
* <li>单设备/多设备登录控制</li>
|
||||
* <li>用户级会话失效</li>
|
||||
* <li>在线用户管理</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 与JWT模式相比,Redis模式支持主动踢人、在线用户查询等功能
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 2024/11/15
|
||||
@@ -55,19 +64,19 @@ public class RedisTokenManager implements TokenManager {
|
||||
String accessToken = IdUtil.fastSimpleUUID();
|
||||
String refreshToken = IdUtil.fastSimpleUUID();
|
||||
|
||||
// 构建用户在线信息
|
||||
OnlineUser onlineUser = new OnlineUser(
|
||||
// 构建用户会话信息
|
||||
UserSession userSession = new UserSession(
|
||||
user.getUserId(),
|
||||
user.getUsername(),
|
||||
user.getDeptId(),
|
||||
user.getDataScope(),
|
||||
user.getDataScopes(),
|
||||
user.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
|
||||
// 存储访问令牌、刷新令牌和刷新令牌映射
|
||||
storeTokensInRedis(accessToken, refreshToken, onlineUser);
|
||||
storeTokensInRedis(accessToken, refreshToken, userSession);
|
||||
|
||||
// 单设备登录控制
|
||||
handleSingleDeviceLogin(user.getUserId(), accessToken);
|
||||
@@ -87,13 +96,13 @@ public class RedisTokenManager implements TokenManager {
|
||||
*/
|
||||
@Override
|
||||
public Authentication parseToken(String token) {
|
||||
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue().get(formatTokenKey(token));
|
||||
if (onlineUser == null) return null;
|
||||
UserSession userSession = (UserSession) redisTemplate.opsForValue().get(formatTokenKey(token));
|
||||
if (userSession == null) return null;
|
||||
|
||||
// 构建用户权限集合
|
||||
Set<SimpleGrantedAuthority> authorities = null;
|
||||
|
||||
Set<String> roles = onlineUser.getRoles();
|
||||
Set<String> roles = userSession.getRoles();
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
authorities = roles.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
@@ -101,7 +110,7 @@ public class RedisTokenManager implements TokenManager {
|
||||
}
|
||||
|
||||
// 构建用户详情对象
|
||||
SysUserDetails userDetails = buildUserDetails(onlineUser, authorities);
|
||||
SysUserDetails userDetails = buildUserDetails(userSession, authorities);
|
||||
return new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
|
||||
}
|
||||
|
||||
@@ -135,12 +144,12 @@ public class RedisTokenManager implements TokenManager {
|
||||
*/
|
||||
@Override
|
||||
public AuthenticationToken refreshToken(String refreshToken) {
|
||||
OnlineUser onlineUser = (OnlineUser) redisTemplate.opsForValue()
|
||||
UserSession userSession = (UserSession) redisTemplate.opsForValue()
|
||||
.get(StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken));
|
||||
if (onlineUser == null) {
|
||||
if (userSession == null) {
|
||||
throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID);
|
||||
}
|
||||
Object oldAccessTokenValue = redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, onlineUser.getUserId()));
|
||||
Object oldAccessTokenValue = redisTemplate.opsForValue().get(StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userSession.getUserId()));
|
||||
// 删除旧的访问令牌记录
|
||||
Optional.of(oldAccessTokenValue)
|
||||
.map(String.class::cast)
|
||||
@@ -148,7 +157,7 @@ public class RedisTokenManager implements TokenManager {
|
||||
|
||||
// 生成新访问令牌并存储
|
||||
String newAccessToken = IdUtil.fastSimpleUUID();
|
||||
storeAccessToken(newAccessToken, onlineUser);
|
||||
storeAccessToken(newAccessToken, userSession);
|
||||
|
||||
int accessTtl = securityProperties.getSession().getAccessTokenTimeToLive();
|
||||
return AuthenticationToken.builder()
|
||||
@@ -166,14 +175,16 @@ public class RedisTokenManager implements TokenManager {
|
||||
@Override
|
||||
public void invalidateToken(String token) {
|
||||
Object value = redisTemplate.opsForValue().get(formatTokenKey(token));
|
||||
if (value instanceof OnlineUser onlineUser) {
|
||||
Long userId = onlineUser.getUserId();
|
||||
if (value instanceof UserSession userSession) {
|
||||
Long userId = userSession.getUserId();
|
||||
invalidateUserSessions(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使指定用户的所有会话失效
|
||||
* <p>
|
||||
* 适用场景:用户修改密码、管理员强制下线、账号封禁等
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
@@ -207,24 +218,26 @@ public class RedisTokenManager implements TokenManager {
|
||||
*
|
||||
* @param accessToken 访问令牌
|
||||
* @param refreshToken 刷新令牌
|
||||
* @param onlineUser 在线用户信息
|
||||
* @param userSession 用户会话信息
|
||||
*/
|
||||
private void storeTokensInRedis(String accessToken, String refreshToken, OnlineUser onlineUser) {
|
||||
private void storeTokensInRedis(String accessToken, String refreshToken, UserSession userSession) {
|
||||
// 访问令牌 -> 用户信息
|
||||
setRedisValue(formatTokenKey(accessToken), onlineUser, securityProperties.getSession().getAccessTokenTimeToLive());
|
||||
setRedisValue(formatTokenKey(accessToken), userSession, securityProperties.getSession().getAccessTokenTimeToLive());
|
||||
|
||||
// 刷新令牌 -> 用户信息
|
||||
String refreshTokenKey = StrUtil.format(RedisConstants.Auth.REFRESH_TOKEN_USER, refreshToken);
|
||||
setRedisValue(refreshTokenKey, onlineUser, securityProperties.getSession().getRefreshTokenTimeToLive());
|
||||
setRedisValue(refreshTokenKey, userSession, securityProperties.getSession().getRefreshTokenTimeToLive());
|
||||
|
||||
// 用户ID -> 刷新令牌
|
||||
setRedisValue(StrUtil.format(RedisConstants.Auth.USER_REFRESH_TOKEN, onlineUser.getUserId()),
|
||||
setRedisValue(StrUtil.format(RedisConstants.Auth.USER_REFRESH_TOKEN, userSession.getUserId()),
|
||||
refreshToken,
|
||||
securityProperties.getSession().getRefreshTokenTimeToLive());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单设备登录控制
|
||||
* <p>
|
||||
* 当配置不允许多设备登录时,新登录会使旧Token失效
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param accessToken 新生成的访问令牌
|
||||
@@ -247,27 +260,27 @@ public class RedisTokenManager implements TokenManager {
|
||||
* 存储新的访问令牌
|
||||
*
|
||||
* @param newAccessToken 新访问令牌
|
||||
* @param onlineUser 在线用户信息
|
||||
* @param userSession 用户会话信息
|
||||
*/
|
||||
private void storeAccessToken(String newAccessToken, OnlineUser onlineUser) {
|
||||
setRedisValue(StrUtil.format(RedisConstants.Auth.ACCESS_TOKEN_USER, newAccessToken), onlineUser, securityProperties.getSession().getAccessTokenTimeToLive());
|
||||
String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, onlineUser.getUserId());
|
||||
private void storeAccessToken(String newAccessToken, UserSession userSession) {
|
||||
setRedisValue(StrUtil.format(RedisConstants.Auth.ACCESS_TOKEN_USER, newAccessToken), userSession, securityProperties.getSession().getAccessTokenTimeToLive());
|
||||
String userAccessKey = StrUtil.format(RedisConstants.Auth.USER_ACCESS_TOKEN, userSession.getUserId());
|
||||
setRedisValue(userAccessKey, newAccessToken, securityProperties.getSession().getAccessTokenTimeToLive());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户详情对象
|
||||
*
|
||||
* @param onlineUser 在线用户信息
|
||||
* @param userSession 用户会话信息
|
||||
* @param authorities 权限集合
|
||||
* @return SysUserDetails 用户详情
|
||||
*/
|
||||
private SysUserDetails buildUserDetails(OnlineUser onlineUser, Set<SimpleGrantedAuthority> authorities) {
|
||||
private SysUserDetails buildUserDetails(UserSession userSession, Set<SimpleGrantedAuthority> authorities) {
|
||||
SysUserDetails userDetails = new SysUserDetails();
|
||||
userDetails.setUserId(onlineUser.getUserId());
|
||||
userDetails.setUsername(onlineUser.getUsername());
|
||||
userDetails.setDeptId(onlineUser.getDeptId());
|
||||
userDetails.setDataScope(onlineUser.getDataScope());
|
||||
userDetails.setUserId(userSession.getUserId());
|
||||
userDetails.setUsername(userSession.getUsername());
|
||||
userDetails.setDeptId(userSession.getDeptId());
|
||||
userDetails.setDataScopes(userSession.getDataScopes());
|
||||
userDetails.setAuthorities(authorities);
|
||||
return userDetails;
|
||||
}
|
||||
|
||||
@@ -70,16 +70,6 @@ public class SecurityUtils {
|
||||
return getUser().map(SysUserDetails::getDeptId).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限范围
|
||||
*
|
||||
* @return Integer
|
||||
*/
|
||||
public static Integer getDataScope() {
|
||||
return getUser().map(SysUserDetails::getDataScope).orElse(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取角色集合
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user