{"record":{"id":"d1f75cd8ef5faff5","repo":"dromara/Sa-Token","slug":"30204","errorCode":"30204","errorMessage":"jwt 已过期：","messagePattern":"jwt 已过期：","errorType":"exception","errorClass":"SaJwtException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java","lineNumber":206,"sourceCode":"    \tJSONObject payloads = jwt.getPayloads();\n    \t\n    \t// 校验 Token 签名\n\t\tboolean verify = jwt.setSigner(createSigner(keyt)).verify();\n    \tif( ! verify) {\n    \t\tthrow new SaJwtException(\"jwt 签名无效：\" + token).setCode(SaJwtErrorCode.CODE_30202);\n    \t}\n\n    \t// 校验 loginType \n    \tif( ! Objects.equals(loginType, payloads.getStr(LOGIN_TYPE))) {\n    \t\tthrow new SaJwtException(\"jwt loginType 无效：\" + token).setCode(SaJwtErrorCode.CODE_30203);\n    \t}\n    \t\n    \t// 校验 Token 有效期\n    \tif(isCheckTimeout) {\n    \t\tLong effTime = payloads.getLong(EFF, 0L);\n        \tif(effTime != NEVER_EXPIRE) {\n        \t\tif(effTime == null || effTime < System.currentTimeMillis()) {\n        \t\t\tthrow new SaJwtException(\"jwt 已过期：\" + token).setCode(SaJwtErrorCode.CODE_30204);\n        \t\t}\n        \t}\n    \t}\n    \t\n        // 返回 \n        return jwt;\n    }\n\n    /**\n     * 获取 jwt 数据载荷 （校验 sign、loginType、timeout） \n     * @param token token值\n     * @param loginType 登录类型 \n     * @param keyt 秘钥 \n     * @return 载荷 \n     */\n    public JSONObject getPayloads(String token, String loginType, String keyt) {\n    \treturn parseToken(token, loginType, keyt, true).getPayloads();\n    }","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java#L188-L224","documentation":"Thrown by SaJwtTemplate.parseToken when isCheckTimeout is true and the token's EFF (effective-until) claim is null or earlier than the current time. Code 30204 marks an expired JWT; NEVER_EXPIRE tokens skip this check.","triggerScenarios":"Parsing a JWT past its embedded expiry while timeout checking is enabled (most read paths enable it). The eff claim is compared against System.currentTimeMillis().","commonSituations":"Client keeps using a token after its lifetime elapsed (JWTs are self-contained, no server-side renewal); app-server clock skew making valid tokens appear expired; token issued with a very short eff during testing; mixing second- and millisecond-scale timestamps when creating tokens manually.","solutions":["Have the client refresh/re-login to obtain a new token when 30204 is returned","Verify server clocks (NTP) on verifying nodes — clock drift directly affects this check","If you forge tokens yourself, ensure the EFF claim uses milliseconds and a future timestamp, or SaTokenDao.NEVER_EXPIRE"],"exampleFix":"// before\n// client keeps a 1h token for days\nparseToken(staleToken, \"login\", secret, true); // 30204\n\n// after\n// client: on 30204, obtain a fresh token and retry\ntry {\n    parseToken(token, \"login\", secret, true);\n} catch (SaJwtException e) {\n    if (isCode(e, SaJwtErrorCode.CODE_30204)) {\n        token = refreshLogin(); // re-login / refresh endpoint\n        parseToken(token, \"login\", secret, true);\n    } else throw e;\n}","handlingStrategy":"retry","validationCode":"JWT jwt = JWT.of(token);\nLong eff = jwt.getPayloads().getLong(\"eff\", 0L);\nboolean expiringSoon = eff != -1 && eff < System.currentTimeMillis() + 60_000L;\nif (expiringSoon) token = refreshToken();","typeGuard":null,"tryCatchPattern":"catch (SaJwtException e) {\n    if (e.getCode() == SaJwtErrorCode.CODE_30204) {\n        token = refreshLogin();\n        return retryOnce(request); // single retry, then fail\n    }\n    throw e;\n}","preventionTips":["Sync verifying servers with NTP — clock skew causes false 30204","Issue short-lived tokens plus a refresh endpoint rather than long-lived JWTs","Proactively refresh tokens near expiry instead of waiting for failure"],"tags":["sa-token","jwt","expiry","clock-skew"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}