{"record":{"id":"d716b5799acd3cf8","repo":"jeecgboot/JeecgBoot","slug":"token-d716b5","errorCode":null,"errorMessage":"Token失效，请重新登录!","messagePattern":"Token失效，请重新登录!","errorType":"exception","errorClass":"AuthenticationException","httpStatus":401,"severity":"warning","filePath":"jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java","lineNumber":146,"sourceCode":"            throw new AuthenticationException(\"Token非法无效!\");\n        }\n\n        // 查询用户信息\n        log.debug(\"———校验token是否有效————checkUserTokenIsEffect——————— \"+ token);\n        LoginUser loginUser = TokenUtils.getLoginUser(username, commonApi, redisUtil);\n        //LoginUser loginUser = commonApi.getUserByName(username);\n        if (loginUser == null) {\n            throw new AuthenticationException(\"用户不存在!\");\n        }\n        // 判断用户状态\n        if (loginUser.getStatus() != 1) {\n            throw new AuthenticationException(\"账号已被锁定,请联系管理员!\");\n        }\n        // 校验token是否超时失效 & 或者账号密码是否错误\n        if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {\n            // 用户登录Token过期提示信息\n            String userLoginTokenErrorMsg = oConvertUtils.getString(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN_ERROR_MSG + token));\n            throw new AuthenticationException(oConvertUtils.isEmpty(userLoginTokenErrorMsg)? CommonConstant.TOKEN_IS_INVALID_MSG: userLoginTokenErrorMsg);\n        }\n        // 代码逻辑说明: 校验用户的tenant_id和前端传过来的是否一致\n        String userTenantIds = loginUser.getRelTenantIds();\n        if(MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL && oConvertUtils.isNotEmpty(userTenantIds)){\n            String contextTenantId = TenantContext.getTenant();\n            log.debug(\"登录租户：\" + contextTenantId);\n            log.debug(\"用户拥有那些租户：\" + userTenantIds);\n             //登录用户无租户，前端header中租户ID值为 0\n            String str =\"0\";\n            if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){\n                // 代码逻辑说明: /issues/I4O14W 用户租户信息变更判断漏洞\n                String[] arr = userTenantIds.split(\",\");\n                if(!oConvertUtils.isIn(contextTenantId, arr)){\n                    boolean isAuthorization = false;\n                    //========================================================================\n                    // 查询用户信息（如果租户不匹配从数据库中重新查询一次用户信息）\n                    String loginUserKey = CacheConstant.SYS_USERS_CACHE + \"::\" + username;\n                    redisUtil.del(loginUserKey);","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/shiro/ShiroRealm.java#L128-L164","documentation":"Thrown by ShiroRealm.checkUserTokenIsEffect() when jwtTokenRefresh() returns false — the token has expired and is no longer refreshable. The method checks the Redis cache (PREFIX_USER_TOKEN + token) for a stored copy; if the cache entry is gone (TTL expired) or the stored token no longer verifies, refresh fails. The actual error message may be overridden by a per-token error message stored in Redis (PREFIX_USER_TOKEN_ERROR_MSG). CommonConstant.TOKEN_IS_INVALID_MSG is the default 'Token失效，请重新登录!' message.","triggerScenarios":"User's session has been idle longer than the JWT max lifetime (JWT TTL * 2); Redis was flushed or restarted losing the token cache; password was changed invalidating the old token signature; the token cache entry was explicitly deleted (e.g., by admin force-logout).","commonSituations":"User leaves the application idle overnight and returns to find their session expired; Redis restart/flush; password change on another device; admin forces all users to re-authenticate by clearing Redis token caches; token TTL configuration is too short.","solutions":["Have the user log in again to obtain a fresh token — this is expected behavior for session expiry.","If sessions expire too quickly, increase the JWT TTL in configuration (jeecg.jwt.expireTime) and the Redis cache TTL accordingly.","Ensure Redis persistence is configured (AOF/RDB) so token caches survive Redis restarts.","Check if a password change or admin action intentionally invalidated the token."],"exampleFix":"// No code fix — expected session expiry.\n// To extend session lifetime, adjust application.yml:\n// jeecg:\n//   jwt:\n//     expireTime: 3600000   # 1 hour JWT TTL (ms)\n//     # Redis cache TTL is automatically set to 2x this value\n// Front-end: on 401 'Token失效', redirect to login page automatically.","handlingStrategy":"try-catch","validationCode":"// Front-end: track token age and proactively refresh before expiry\nconst tokenAge = Date.now() - parseInt(localStorage.getItem('token-issued-at'));\nif (tokenAge > TOKEN_TTL * 0.8) {\n    await refreshToken(); // or redirect to login\n}","typeGuard":null,"tryCatchPattern":"// Handled by JwtFilter — returns 401 'Token失效'\n// Front-end interceptor:\naxios.interceptors.response.use(null, error => {\n    if (error.response?.status === 401) {\n        store.dispatch('Logout');\n        router.push('/user/login');\n    }\n});","preventionTips":["Configure JWT TTL to match expected session duration (jeecg.jwt.expireTime).","Ensure Redis persistence (AOF/RDB) so token caches survive restarts.","Use front-end token refresh/silent-renew before expiry.","After password changes, clear old tokens from Redis."],"tags":["shiro","jwt","authentication","token-expiry","session","redis"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}