多版本共用redis导致数据没及时更新报错

项目共用redis

问题描述

java 复制代码
当前项目是v2版本,v1版本和这代码用户登录和获取token验证一样逻辑,一样都是springsecurity,redis一致。jwt也一致导致同一个token可以两个项目都能使用。但是v1和v2共用一个用户中心服务,但是他们角色roles,设备deviceIds,permissions不一致,导致v1登录后redis,切换v2项目不用重新登录,v2根据请求token最后获取到v1项目的redis缓存信息
{"@type":"com.skms.common.core.domain.model.LoginUser","browser":"Chrome 14","expireTime":1778550957853,"ipaddr":"10.168.88.249","loginLocation":"内网IP","loginTime":1778549157853,"os":"Windows 10","permissions":Set["system:user:resetPwd","base:ukey:delFile","monitor:logininfor:remove","system:user:query","system:use:remove","base:keyHistory:list","system:user:export","secure:cert:list","secure:cert:request","system:page:query","system:role:remove","skms:log:remove","base:key:remove","base:keyDistribute:add","base:keyDesc:remove","fisherman:card:backup","base:symmetricKey:importKeyPart","base:node:add","base:keyHistory:remove","base:nodeCreate:remove","base:node:import","monitor:logininfor:list","base:symmetricKey:list","base:keyDesc:edit","base:node:list","skms:ukey:edit","secure:cert:remove","base:key:export","kms:online:revoke","system:create:list","base:keyDistribute:keyGraph","system:create:add","system:user:edit","secure:cardUser:delete","system:role:edit","base:ukey:listFile","skms:ukey:list","monitor:operlog:remove","base:symmetricKey:add","skms:ukey:export","system:role:add","system:user:bindUKey","skms:ukey:remove","kms:offlinekey:recovery","monitor:logininfor:query","secure:cert:caCert","base:key:add","base:node:remove","base:keyHistory:add","skms:log:query","base:symmetricKey:importUkey","base:nodeCreate:query","base:keyDesc:add","system:role:export","skms:keys:generate","base:keyHistory:query","secure:cert:export","system:create:query","base:page:edit","kms:online:publish","base:nodeCreate:edit","monitor:operlog:export","kms:online:enable","base:symmetricKey:updateStatus","system:use:query","base:keyHistory:edit","system:user:add","base:keyDistribute:edit","monitor:operlog:add","base:nodeCreate:export","base:nodeCreate:list","skms:log:export","kms:offlinekey:export","base:keyDistribute:query","base:keyDistribute:remove","base:ukey:readFile","base:nodeCreate:add","base:node:query","system:use:add","base:symmetricKey:exportKeyPart","system:create:remove","base:symmetricKey:edit","base:key:edit","kms:online:backup","secure:cardUser:edit","monitor:operlog:edit","fisherman:card:changePin","base:symmetricKey:distributeKey","system:user:remove","base:symmetricKey:query","system:role:list","system:user:import","secure:cardUser:add","base:keyDistribute:export","base:keyHistory:export","skms:ukey:add","kms:online:recovery","monitor:operlog:list","system:use:edit","base:symmetricKey:exportUkey","monitor:logininfor:export","system:use:export","monitor:operlog:query","skms:log:list","system:user:list","base:symmetricKey:reconfig","fisherman:card:login","base:symmetricKey:remove","kms:online:judiciary","secure:cardUser:info","secure:cert:signCert","base:node:edit","skms:ukey:query","fisherman:card:recovery","system:role:query","fisherman:card:logout","base:symmetricKey:export"],"token":"a798cb45-e7ce-409b-ae0b-ac3c92684d61","user":{"@type":"com.skms.common.core.domain.entity.FeignSysUser","admin":false,"avatar":"1","certId":72,"createBy":"admin","createTime":1755738648000,"delFlag":"0","deviceIds":[],"email":"","keyCode":"GKLZ022508023","loginDate":1778492175000,"loginIp":"10.168.88.249","nickName":"xtgly","params":{"@type":"java.util.LinkedHashMap"},"password":"$2a$10$qFyCTChYPzfxVr.kDym0NOT4b/evfbFQw908RSFDEaOu/QkCDwCT2","phonenumber":"","roleIds":[2L,7L,8L,9L],"roles":[{"admin":false,"dataScope":"1","deptCheckStrictly":false,"flag":false,"menuCheckStrictly":false,"params":{"@type":"java.util.HashMap"},"permissions":Set["monitor:operlog:export","monitor:operlog:query","skms:log:list","skms:log:query","monitor:logininfor:remove","monitor:operlog:add","monitor:logininfor:list","monitor:operlog:remove","monitor:operlog:list","skms:log:remove","skms:log:export","monitor:logininfor:query","monitor:logininfor:export","monitor:operlog:edit"],"roleId":2,"roleKey":"auditManager","roleName":"审计管理员","roleSort":"3"},{"admin":false,"dataScope":"1","deptCheckStrictly":false,"flag":false,"menuCheckStrictly":false,"params":{"@type":"java.util.HashMap"},"permissions":Set["system:user:resetPwd","fisherman:card:changePin","system:user:remove","secure:cert:remove","system:role:list","system:user:import","secure:cardUser:add","system:user:edit","secure:cardUser:delete","system:user:query","system:role:edit","system:user:add","system:user:export","secure:cert:list","secure:cert:request","system:role:remove","system:role:add","system:user:bindUKey","secure:cert:caCert","fisherman:card:backup","system:user:list","fisherman:card:login","system:role:export","secure:cardUser:info","secure:cert:signCert","secure:cert:export","secure:cardUser:edit","fisherman:card:recovery","system:role:query","fisherman:card:logout"],"roleId":7,"roleKey":"systemManager","roleName":"系统管理员","roleSort":"2"},{"admin":false,"dataScope":"1","deptCheckStrictly":false,"flag":false,"menuCheckStrictly":false,"params":{"@type":"java.util.HashMap"},"permissions":Set["base:ukey:delFile","kms:online:enable","base:symmetricKey:updateStatus","system:use:query","system:use:remove","base:keyHistory:edit","base:keyDistribute:edit","base:keyHistory:list","system:page:query","base:nodeCreate:export","base:nodeCreate:list","base:key:remove","base:keyDistribute:add","kms:offlinekey:export","base:keyDesc:remove","base:keyDistribute:query","base:keyDistribute:remove","base:ukey:readFile","base:symmetricKey:importKeyPart","base:node:add","base:nodeCreate:add","base:keyHistory:remove","base:node:query","base:nodeCreate:remove","system:use:add","base:symmetricKey:exportKeyPart","system:create:remove","base:symmetricKey:edit","base:node:import","base:key:edit","kms:online:backup","base:symmetricKey:distributeKey","base:symmetricKey:list","base:keyDesc:edit","base:node:list","skms:ukey:edit","base:key:export","base:symmetricKey:query","kms:online:revoke","system:create:list","base:keyDistribute:keyGraph","system:create:add","base:keyDistribute:export","base:ukey:listFile","base:keyHistory:export","skms:ukey:add","skms:ukey:list","kms:online:recovery","base:symmetricKey:add","skms:ukey:export","skms:ukey:remove","system:use:edit","base:symmetricKey:exportUkey","base:key:add","system:use:export","base:node:remove","base:keyHistory:add","base:symmetricKey:reconfig","base:symmetricKey:remove","base:symmetricKey:importUkey","base:nodeCreate:query","base:keyDesc:add","base:node:edit","skms:keys:generate","skms:ukey:query","base:keyHistory:query","system:create:query","base:page:edit","kms:online:publish","base:nodeCreate:edit","base:symmetricKey:export"],"roleId":8,"roleKey":"businessManager","roleName":"业务管理员","roleSort":"4"},{"admin":false,"dataScope":"1","deptCheckStrictly":false,"flag":false,"menuCheckStrictly":false,"params":{"@type":"java.util.HashMap"},"permissions":Set["kms:offlinekey:recovery","kms:online:judiciary"],"roleId":9,"roleKey":"judicatureManager","roleName":"司法恢复管理员","roleSort":"5"}],"sex":"0","status":"0","updateBy":"nothing","updateTime":1778463375000,"userId":38,"userName":"xtgly"},"userId":38,"username":"xtgly"}
导致PermissionService里面hasPermi 报错,明明用户在v2关联的权限,但是实际还是获取到v1的权限,导致的。
有啥办法在缓存加个标识(或者在保存redis的数据里加一个标识,获取发现不是当前版本,重新往redis赋值当前版本并更新springSecurity上下文)

ai回答:
这是一个典型的多版本应用共享 Redis 缓存导致的数据不一致问题。v1 和 v2 共用同一个用户中心服务和相同的 JWT token,但权限体系不同,导致 v2 请求时会获取到 v1 缓存的权限信息。

java 复制代码
v1 登录: v1 登录后 Redis 缓存的 LoginUser.appVersion = "v1"
v2 访问: v2 使用 v1 的 token 访问
JwtAuthenticationTokenFilter.getLoginUser() 从 Redis 获取 LoginUser
检查 appVersion ("v1") != 当前版本 ("v2")
返回 null 触发重新获取
重新获取: 通过 UserClientApi 从用户中心获取最新用户信息
刷新缓存: 使用 v2 的权限重新构建 LoginUser,设置 appVersion = "v2",刷新 Redis
设置 SecurityContext: 将新的 LoginUser 设置到 SecurityContext

一、登录流程(v1和v2都是一样的)

参考之前blog 用户登录:温故一下(验证码和jwt简单创建和验证)
(一定要看,重点看UserDetailsServiceImpl , TokenService, JwtAuthenticationTokenFilter )

大概描述一下:

登录:

1.登录验证

2.生成token并保存用户信息到redis

java 复制代码
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername(登录验证)
           Authentication  authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 生成token并保存用户信息到redis
        return tokenService.createToken(loginUser);

和blog 用户登录:温故一下(验证码和jwt简单创建和验证)里UserDetailsService相比:

loadUserByUsername 原来是从本地获取用户信息和权限,菜单等一系列数据,现在这个远程获取用户信息,但是权限,菜单等还要用本地的(当前版本)

java 复制代码
/**
 * 用户验证处理
 *
 * @author skms
 */
@Service("UserDetailsServiceImpl")
public class UserDetailsServiceImpl implements UserDetailsService {
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Resource
    private ISysUserService userService;

    @Resource
    private ISysUserDeviceService sysUserDeviceService;

    @Resource
    private SysPermissionService permissionService;

    @Resource
    private UserClientApi userClientApi;


    @Value("${kms.userCenter}")
    private String userCenter;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        SysUser user = userService.selectUserByUserName(username);
        log.info("配置的 userCenter: {}", userCenter);
        FeignSysUser internalInfoByUserName = userClientApi.getInternalInfoByUserName(username);
        // 补充角色信息:根据远程返回的用户 ID 查询本地角色表获取 roles , roleIds ,deviceIds
        if (internalInfoByUserName != null && internalInfoByUserName.getUserId() != null) {
            // 查询角色 ID 列表
            List<Long> roleIds = userService.selectRoleIdsByUserId(internalInfoByUserName.getUserId());
            internalInfoByUserName.setRoleIds(roleIds.toArray(new Long[0]));
            // 查询角色对象列表
            List<SysRole> roles = userService.selectRolesByUserId(internalInfoByUserName.getUserId());
            internalInfoByUserName.setRoles(roles);
            List<String> deviceIds = sysUserDeviceService.selectSysDeviceIdsByUserId(internalInfoByUserName.getUserId());
            internalInfoByUserName.setDeviceIds(deviceIds);
        }
        SysUser user = FeignTransferUser.toFeignSysUser(internalInfoByUserName);
        if (StringUtils.isNull(user)) {
            log.info("登录用户:{} 不存在.", username);
            throw new RuntimeException("登录用户:" + username + " 不存在");
        } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
            log.info("登录用户:{} 已被删除.", username);
            throw new RuntimeException("对不起,您的账号:" + username + " 已被删除");
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户:{} 已被停用.", username);
            throw new RuntimeException("对不起,您的账号:" + username + " 已停用");
        }

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user) {
        return new LoginUser(user.getUserId(), user, permissionService.getMenuPermission(user));
    }
}

在blog 用户登录:温故一下(验证码和jwt简单创建和验证) 的

TokenService 可以看到

createToken 先保存缓存,调用 refreshToken 保存到redis

JwtAuthenticationTokenFilter 可以看到从redis去用户信息去设置springSecurity的上下文

二、在保存redis用户信息里面,给用户加一个标识

配置文件(v1和v2 jwt 令牌一致,每个项目各自加各自的appVersion 标识)

yaml 复制代码
# token配置
token:
    # 令牌自定义标识
    header: Authorization
    # 令牌密钥
    secret: abcdefghijklmnopqrstuvwxyz
    # 令牌有效期(默认30分钟)
    expireTime: 30
    appVersion: v2  # 新增:应用版本标识
  

实体类UserDetails ,和blog 用户登录:温故一下(验证码和jwt简单创建和验证)里面实体类相比:多一个字段 appVersion

java 复制代码
/**
 * 登录用户身份权限
 *
 * @author skms
 */
public class LoginUser implements UserDetails {
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 权限列表
     */
    private Set<String> permissions;

    /**
     * 用户信息
     */
    private SysUser user;

    /**
     * 应用版本标识
     */
    private String appVersion;

    public LoginUser() {
    }

    public LoginUser(SysUser user, Set<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    public LoginUser(Long userId, SysUser user, Set<String> permissions) {
        this.userId = userId;
        this.user = user;
        this.permissions = permissions;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    @JSONField(serialize = false)
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUserName();
    }

    /**
     * 账户是否未过期,过期无法验证
     */
    @JSONField(serialize = false)
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     *
     * @return 结果
     */
    @JSONField(serialize = false)
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 是否可用 ,禁用的用户不能身份验证
     *
     * @return 结果
     */
    @JSONField(serialize = false)
    @Override
    public boolean isEnabled() {
        return true;
    }

    public Long getLoginTime() {
        return loginTime;
    }

    public void setLoginTime(Long loginTime) {
        this.loginTime = loginTime;
    }

    public String getIpaddr() {
        return ipaddr;
    }

    public void setIpaddr(String ipaddr) {
        this.ipaddr = ipaddr;
    }

    public String getLoginLocation() {
        return loginLocation;
    }

    public void setLoginLocation(String loginLocation) {
        this.loginLocation = loginLocation;
    }

    public String getBrowser() {
        return browser;
    }

    public void setBrowser(String browser) {
        this.browser = browser;
    }

    public String getOs() {
        return os;
    }

    public void setOs(String os) {
        this.os = os;
    }

    public Long getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(Long expireTime) {
        this.expireTime = expireTime;
    }

    public Set<String> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<String> permissions) {
        this.permissions = permissions;
    }

    public SysUser getUser() {
        return user;
    }

    public void setUser(SysUser user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    public String getAppVersion() {
        return appVersion;
    }

    public void setAppVersion(String appVersion) {
        this.appVersion = appVersion;
    }
}

三、TokenService

和blog 用户登录:温故一下(验证码和jwt简单创建和验证)里面TokenService 相比

1.获取用户身份信息 判断是否是当前版本

2.增加refreshUserFromUserCenter:从用户中心重新获取用户信息(适用于版本不匹配场景)

3.refreshToken:redis保存时候增加版本标识

java 复制代码
/**
 * token 验证处理
 *
 * @author skms
 */
@Slf4j
@Component
public class TokenService {
    protected static final long MILLIS_SECOND = 1000;
    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
    /**
     * 令牌自定义标识
     */
    @Value("${token.header}")
    private String header;
    /**
     * 令牌秘钥
     */
    @Value("${token.secret}")
    private String secret;
    /**
     * 令牌有效期(默认 30 分钟)
     */
    @Value("${token.expireTime}")
    private int expireTime;
    /**
     * 应用版本标识
     */
    @Value("${token.appVersion:v2}")
    private String appVersion;

    @Resource
    private RedisCache redisCache;
    @Resource
    private ISysUserService userService;
    @Resource
    private UserClientApi userClientApi;
    @Resource
    private SysPermissionService permissionService;
    @Resource
    private ISysUserDeviceService sysUserDeviceService;
    @Resource
    private ICertService certService;
    @Resource
    @Qualifier("UserDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            try {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser loginUser = redisCache.getCacheObject(userKey);
                // 验证版本是否匹配
                if (loginUser != null && !isCurrentVersion(loginUser)) {
                    // 版本不匹配,返回 null 触发重新获取
                    return null;
                }
                return loginUser;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser) {
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token) {
        if (StringUtils.isNotEmpty(token)) {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
    }

    /**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(LoginUser loginUser) {
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        setUserAgent(loginUser);
        refreshToken(loginUser);

        Map<String, Object> claims = new HashMap<>(16);
        claims.put(Constants.LOGIN_USER_KEY, token);
        return createToken(claims);
    }

    /**
     * 验证令牌是否有效(未过期)
     *
     * @param loginUser 登录用户
     * @return 是否有效
     */
    public boolean isTokenValid(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        // Token 未过期且有效期不足 20 分钟时才认为是有效的
        return expireTime - currentTime > MILLIS_MINUTE_TEN;
    }

    /**
     * 验证令牌有效期,相差不足 20 分钟,自动刷新缓存
     *
     * @param loginUser 登录用户
     */
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 设置应用版本标识
        loginUser.setAppVersion(appVersion);
        // 根据 uuid 将 loginUser 缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }

    /**
     * 验证用户缓存是否为当前应用版本
     */
    private boolean isCurrentVersion(LoginUser loginUser) {
        String currentVersion = appVersion != null ? appVersion : Constants.APP_VERSION_V2;
        return currentVersion.equals(loginUser.getAppVersion());
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser) {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims parseToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getSubject();
    }

    /**
     * 获取请求 token
     *
     * @param request 请求
     * @return token
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

    private String getTokenKey(String uuid) {
        return Constants.LOGIN_TOKEN_KEY + uuid;
    }

    /**
     * 从用户中心重新获取用户信息(适用于版本不匹配场景)
     */
    public LoginUser refreshUserFromUserCenter(HttpServletRequest request) {
        String token = getToken(request);
        if (StringUtils.isEmpty(token)) {
            return null;
        }

        try {
            Claims claims = parseToken(token);
            Object userIdObj = claims.get(Constants.JWT_USERID);
            Long userId;
            if (userIdObj instanceof Integer) {
                userId = ((Integer) userIdObj).longValue();
            } else if (userIdObj instanceof Long) {
                userId = (Long) userIdObj;
            } else {
                log.error("重新获取用户信息:userId 类型错误,类型为{}", userIdObj == null ? "null" : userIdObj.getClass().getName());
                return null;
            }
            if (userId == null) {
                log.error("重新获取用户信息:userId 不存在.");
                return null;
            }

            // 重新加载本地角色和设备信息(使用 v2 的数据)
            List<Long> roleIds = userService.selectRoleIdsByUserId(feignSysUser.getUserId());
            feignSysUser.setRoleIds(roleIds.toArray(new Long[0]));

            List<SysRole> roles = userService.selectRolesByUserId(feignSysUser.getUserId());
            feignSysUser.setRoles(roles);

            List<String> deviceIds = sysUserDeviceService.selectSysDeviceIdsByUserId(feignSysUser.getUserId());
            feignSysUser.setDeviceIds(deviceIds);

            // 构建新的 LoginUser(使用 v2 的权限)
            SysUser user = FeignTransferUser.toFeignSysUser(feignSysUser);
            if (StringUtils.isNull(user)) {
                log.error("重新获取用户信息:{} 不存在.", uuid);
                throw new UsernameNotFoundException("重新获取用户信息:" + uuid + " 不存在");
            } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
                log.error("重新获取用户信息:{} 已被删除.", uuid);
                throw new BaseException("对不起,您的账号:" + uuid + " 已被删除");
            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
                log.error("重新获取用户信息:{} 已被停用.", uuid);
                throw new BaseException("对不起,您的账号:" + uuid + " 已停用");
            }

            LoginUser loginUser = new LoginUser(feignSysUser.getUserId(), user, permissionService.getMenuPermission(user));
            loginUser.setToken(token);

            // 刷新 Redis 缓存(带新版本号)
            refreshToken(loginUser);

            return loginUser;
        } catch (Exception e) {
            log.error("重新获取用户信息失败", e);
            return null;
        }
    }
}

四、JwtAuthenticationTokenFilter token过滤器

和blog 用户登录:温故一下(验证码和jwt简单创建和验证)里面JwtAuthenticationTokenFilter相比

1.重新获取用户信息并设置给springSecurity的上下文

java 复制代码
/**
 * token 过滤器 验证 token 有效性
 *
 * @author skms
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Resource
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        LoginUser loginUser = tokenService.getLoginUser(request);

        // 情况 1: 版本不匹配或缓存不存在,loginUser 为 null
        // 情况 2: SecurityContext 中没有认证信息
        if (StringUtils.isNull(SecurityUtils.getAuthentication())) {
            if (loginUser == null) {
                // 版本不匹配或缓存不存在,通过用户中心重新获取权限并刷新缓存
                loginUser = tokenService.refreshUserFromUserCenter(request);
            } else if (!tokenService.isTokenValid(loginUser)) {
                // Token 过期,刷新缓存
                tokenService.verifyToken(loginUser);
            }

            if (StringUtils.isNotNull(loginUser)) {
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}
相关推荐
taocarts_bidfans1 小时前
Taoify与Redis、Nginx集成实战:提升跨境独立站性能与并发能力
数据库·redis·nginx·跨境电商·独立站
wang3zc1 小时前
CSS如何实现元素镜像翻转_使用transformscalex负值
jvm·数据库·python
CLX05051 小时前
Golang如何做图片处理缩放_Golang图片处理教程【收藏】
jvm·数据库·python
MongoDB 数据平台1 小时前
官宣:MongoDB 正式内置到 Claude Code
数据库·mongodb
TEC_INO1 小时前
Linux57:读取人脸数据库并保存到map
数据库·oracle
阿苟1 小时前
数据库重点难点
redis·后端·mysql
原来是猿1 小时前
TCP Echo Server 深度解析:从单进程到线程池的演进之路(下)
linux·服务器·数据库
2301_812539671 小时前
mysql如何限制用户连接数_使用MAX_USER_CONNECTIONS优化并发
jvm·数据库·python
MongoDB 数据平台1 小时前
MongoDB 驱动效能革新:盖雅工场报表查询效率跃升8倍
数据库·mongodb