【若依框架】从登录到鉴权:剖析自定义Session与JWT Token的协同工作流

发布时间:2026/7/16 7:48:52
【若依框架】从登录到鉴权:剖析自定义Session与JWT Token的协同工作流 1. 若依框架认证体系设计精要若依框架采用了一种独特的无状态Token承载有状态Session的混合认证模式这种设计既保留了传统Session管理的灵活性又具备了JWT无状态的优势。在实际项目中这种设计能有效平衡安全性和性能需求。核心设计思想可以概括为用轻量级的JWT作为传输载体内部通过Redis维护完整的会话状态。具体实现上当用户登录成功后服务端生成UUID作为会话标识符Session Key将会话数据LoginUser对象存入Redis将Session Key嵌入JWT的claims中将JWT返回给客户端这种设计的巧妙之处在于对外表现为无状态的JWT避免传统Session的粘性问题对内通过Redis维护会话状态可以灵活控制会话生命周期JWT中只存储最小必要信息Session Key避免敏感数据泄露2. 登录流程深度解析让我们通过一个完整的用户登录场景拆解若依的认证流程2.1 前端登录交互前端登录页面提交表单时会触发以下关键操作// Vue组件中的登录方法 handleLogin() { this.$refs.loginForm.validate(valid { if (valid) { this.loading true this.$store.dispatch(Login, this.loginForm).then(() { this.$router.push({ path: this.redirect || / }) }) } }) }这段代码完成了表单验证调用Vuex的Login action登录成功后跳转到首页2.2 后端认证处理链后端处理登录请求的核心逻辑位于SysLoginControllerPostMapping(/login) public String login(RequestBody LoginBody loginBody) { // 验证验证码 String verifyKey Constants.CAPTCHA_CODE_KEY loginBody.getUuid(); String captcha redisCache.getCacheObject(verifyKey); if (captcha null || !captcha.equals(loginBody.getCode())) { throw new CaptchaExpireException(); } // 用户认证 Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginBody.getUsername(), loginBody.getPassword())); // 生成Token LoginUser loginUser (LoginUser) authentication.getPrincipal(); return tokenService.createToken(loginUser); }认证流程分为三个关键阶段验证码校验从Redis获取并比对验证码身份认证通过Spring Security的AuthenticationManager进行认证令牌生成认证成功后调用TokenService生成令牌3. Token生成机制剖析TokenService是认证体系的核心组件其createToken方法实现了完整的令牌生成逻辑3.1 Session Key生成public String createToken(LoginUser loginUser) { // 生成UUID作为Session Key String token IdUtils.fastUUID(); loginUser.setToken(token); // 设置用户客户端信息 setUserAgent(loginUser); // 刷新Token有效期 refreshToken(loginUser); // 构建JWT Claims MapString, Object claims new HashMap(); claims.put(Constants.LOGIN_USER_KEY, token); // 生成JWT return createToken(claims); }这里有几个关键点需要注意方法参数名token实际指的是Session Key容易产生误解refreshToken方法将会话数据存入RedisJWT的claims中只存储Session Key3.2 Redis会话存储refreshToken方法处理Redis存储逻辑public void refreshToken(LoginUser loginUser) { // 设置登录时间 loginUser.setLoginTime(System.currentTimeMillis()); // 计算过期时间默认30分钟 loginUser.setExpireTime(loginUser.getLoginTime() expireTime * 60 * 1000); // 存储到Redis String userKey getTokenKey(loginUser.getToken()); redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); }Redis存储结构示例Key: login_tokens:8e14a8f0-672c-4e53-8a1d-5c5b0f8f3e1c Value: { token: 8e14a8f0-672c-4e53-8a1d-5c5b0f8f3e1c, userid: 1, username: admin, ipaddr: 192.168.1.100, loginTime: 1659345678901, expireTime: 1659347478901, permissions: [system:user:list, system:role:edit], roles: [admin] }3.3 JWT生成细节最终的JWT生成由私有方法createToken完成private String createToken(MapString, Object claims) { return Jwts.builder() .setClaims(claims) // 只包含Session Key .signWith(SignatureAlgorithm.HS512, secret) // HS512算法签名 .compact(); }生成的JWT结构示例Header: { alg: HS512 } Payload: { LOGIN_USER_KEY: 8e14a8f0-672c-4e53-8a1d-5c5b0f8f3e1c, iat: 1659345678 } Signature: HMACSHA512(base64UrlEncode(header) . base64UrlEncode(payload), secret)4. 请求鉴权流程详解当客户端携带JWT访问受保护接口时若依通过过滤器链完成鉴权4.1 JWT认证过滤器核心过滤器JwtAuthenticationTokenFilter的处理流程Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { // 从请求中获取LoginUser LoginUser loginUser tokenService.getLoginUser(request); if (loginUser ! null SecurityUtils.getAuthentication() null) { // 验证Token有效性 tokenService.verifyToken(loginUser); // 构建Authentication对象 UsernamePasswordAuthenticationToken authentication new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 存入SecurityContext SecurityContextHolder.getContext().setAuthentication(authentication); } chain.doFilter(request, response); }4.2 Token验证过程TokenService.verifyToken方法实现了完整的令牌验证public void verifyToken(LoginUser loginUser) { // 检查过期时间 long expireTime loginUser.getExpireTime(); long currentTime System.currentTimeMillis(); if (expireTime - currentTime MILLIS_MINUTE_TEN) { // 小于10分钟时自动续期 refreshToken(loginUser); } }这里有个贴心的设计当Token剩余有效期不足10分钟时系统会自动续期避免用户在操作过程中突然登出。4.3 用户态管理若依提供了便捷的用户态访问方式在任何地方都可以通过以下方式获取当前用户// 获取当前登录用户 LoginUser loginUser tokenService.getLoginUser(request); // 获取用户ID Long userId loginUser.getUserId(); // 获取用户权限 SetString permissions loginUser.getPermissions();这种设计使得业务代码可以方便地获取用户上下文而不需要处理复杂的认证细节。5. 前后端协同工作流完整的认证流程需要前后端协同配合下面我们看看前端如何处理认证信息5.1 前端Token管理登录成功后前端将Token存储在Vuex和Cookie中// user.js store模块 Login({ commit }, userInfo) { return new Promise((resolve, reject) { login(userInfo).then(res { const token res.data.access_token commit(SET_TOKEN, token) setToken(token) // 存入Cookie resolve() }) }) }5.2 请求拦截器前端通过axios拦截器自动附加Token到请求头service.interceptors.request.use(config { // 是否需要设置token const isToken (config.headers || {}).isToken false if (getToken() !isToken) { config.headers[Authorization] Bearer getToken() } return config })这种设计使得业务代码无需关心Token的传递问题实现了认证逻辑的透明化。6. 安全性设计考量若依的认证体系在安全性方面做了多重保障验证码防护登录时必须提供有效的验证码防止暴力破解HTTPS传输建议生产环境启用HTTPS防止Token被截获短期有效期默认30分钟有效期减少Token泄露风险敏感操作验证关键操作需要二次认证Token黑名单支持Token的主动失效机制对于特别敏感的系统还可以进一步实现设备指纹绑定增加异地登录检测加入操作行为分析7. 扩展与定制实践在实际项目中我们可能需要扩展默认的认证机制7.1 多端登录支持通过改造TokenService可以实现不同终端的并行登录public String createToken(LoginUser loginUser, String deviceType) { String token IdUtils.fastUUID(); loginUser.setToken(token); loginUser.setDeviceType(deviceType); // 添加设备类型标识 refreshToken(loginUser); MapString, Object claims new HashMap(); claims.put(Constants.LOGIN_USER_KEY, token); claims.put(device, deviceType); // 将设备类型加入JWT return createToken(claims); }7.2 权限缓存优化对于权限系统可以加入本地缓存减少Redis访问public LoginUser getLoginUser(HttpServletRequest request) { String token getToken(request); if (StringUtils.isEmpty(token)) { return null; } // 先从本地缓存获取 LoginUser loginUser localCache.get(token); if (loginUser ! null) { return loginUser; } // 本地缓存不存在则从Redis获取 Claims claims parseToken(token); String uuid (String) claims.get(Constants.LOGIN_USER_KEY); loginUser redisCache.getCacheObject(getTokenKey(uuid)); if (loginUser ! null) { localCache.put(token, loginUser); // 存入本地缓存 } return loginUser; }7.3 分布式会话方案在集群环境下可以通过以下方式优化会话管理Redis集群部署保证会话存储的高可用本地二级缓存减少网络开销会话同步机制确保关键操作的一致性我在实际项目中曾遇到过Redis连接不稳定导致认证失败的问题最终通过以下方案解决引入本地Caffeine缓存作为Redis的降级方案实现双写策略同时更新Redis和本地缓存增加心跳检测及时发现Redis连接异常