{"record":{"id":"c5fb3beb591184f9","repo":"dromara/Sa-Token","slug":"30111","errorCode":"30111","errorMessage":"无效 refresh_token: ","messagePattern":"无效 refresh_token: ","errorType":"exception","errorClass":"SaOAuth2RefreshTokenException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java","lineNumber":558,"sourceCode":"\n\t/**\n\t * 获取 RefreshTokenModel，无效的 RefreshToken 会返回 null\n\t * @param refreshToken /\n\t * @return /\n\t */\n\tpublic RefreshTokenModel getRefreshToken(String refreshToken) {\n\t\treturn SaOAuth2Manager.getDao().getRefreshToken(refreshToken);\n\t}\n\n\t/**\n\t * 校验 Refresh-Token，成功返回 RefreshTokenModel，失败则抛出异常\n\t * @param refreshToken /\n\t * @return /\n\t */\n\tpublic RefreshTokenModel checkRefreshToken(String refreshToken) {\n\t\tRefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken);\n\t\tif(rt == null) {\n\t\t\tthrow new SaOAuth2RefreshTokenException(\"无效 refresh_token: \" + refreshToken)\n\t\t\t\t\t.setRefreshToken(refreshToken)\n\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30111);\n\t\t}\n\t\treturn rt;\n\t}\n\n\t/**\n\t * 获取 Refresh-Token 列表：此应用下 对 某个用户 签发的所有 Refresh-Token\n\t *\n\t * @param clientId /\n\t * @param loginId /\n\t * @return /\n\t */\n\tpublic List<String> getRefreshTokenValueList(String clientId, Object loginId) {\n\t\treturn SaOAuth2Manager.getDao().getRefreshTokenValueList_FromAdjustAfter(clientId, loginId);\n\t}\n\n\t/**","sourceCodeStart":540,"sourceCodeEnd":576,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java#L540-L576","documentation":"Thrown by SaOAuth2Template.checkRefreshToken when the refresh token is not found in the DAO (code 30111, SaOAuth2RefreshTokenException). Refresh tokens are server-issued and stored; a lookup miss means it was never issued, expired, revoked (e.g. when its access token family was revoked), or storage was lost.","triggerScenarios":"Calling /oauth2/token?grant_type=refresh_token with an expired, already-revoked, mistyped, or fabricated refresh token.","commonSituations":"Long-lived refresh token past its timeout; server restart with in-memory DAO; Redis eviction; token family revoked by user logout/revoke; front-end persisted an old refresh token after re-login.","solutions":["Fall back to a full re-authorization (authorize flow) to obtain new tokens when the refresh token is dead","Persist refresh tokens client-side only as issued, and update stored values whenever a refresh response arrives","Use Redis-backed DAO and check refresh-token timeout configuration on the server"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"if (SaOAuth2Manager.getDao().getRefreshToken(refreshToken) == null) { /* dead -> reauthorize */ }","typeGuard":null,"tryCatchPattern":"try { return saOAuth2Template.checkRefreshToken(rt); } catch (SaOAuth2RefreshTokenException e) { return fullReauthorizationFlow(); }","preventionTips":["Store the latest refresh token atomically when rotating","Treat refresh failure as a signal to re-authenticate, not to retry the same token"],"tags":["oauth2","refresh-token","validation","token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}