dromara/Sa-Token · error · SaOAuth2RefreshTokenException
30111
30111
Error message
无效 refresh_token:
What it means
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.
Source
Thrown at sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java:558
/**
* 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null
* @param refreshToken /
* @return /
*/
public RefreshTokenModel getRefreshToken(String refreshToken) {
return SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
}
/**
* 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常
* @param refreshToken /
* @return /
*/
public RefreshTokenModel checkRefreshToken(String refreshToken) {
RefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
if(rt == null) {
throw new SaOAuth2RefreshTokenException("无效 refresh_token: " + refreshToken)
.setRefreshToken(refreshToken)
.setCode(SaOAuth2ErrorCode.CODE_30111);
}
return rt;
}
/**
* 获取 Refresh-Token 列表:此应用下 对 某个用户 签发的所有 Refresh-Token
*
* @param clientId /
* @param loginId /
* @return /
*/
public List<String> getRefreshTokenValueList(String clientId, Object loginId) {
return SaOAuth2Manager.getDao().getRefreshTokenValueList_FromAdjustAfter(clientId, loginId);
}
/**View on GitHub (pinned to ac2c7f6e94)
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
Defensive patterns
Strategy: fallback
Validate before calling
if (SaOAuth2Manager.getDao().getRefreshToken(refreshToken) == null) { /* dead -> reauthorize */ } Try / catch
try { return saOAuth2Template.checkRefreshToken(rt); } catch (SaOAuth2RefreshTokenException e) { return fullReauthorizationFlow(); } Prevention
- Store the latest refresh token atomically when rotating
- Treat refresh failure as a signal to re-authenticate, not to retry the same token
When it happens
Trigger: Calling /oauth2/token?grant_type=refresh_token with an expired, already-revoked, mistyped, or fabricated refresh token.
Common situations: 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.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/c5fb3beb591184f9.
Report an issue: GitHub.