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

  1. Fall back to a full re-authorization (authorize flow) to obtain new tokens when the refresh token is dead
  2. Persist refresh tokens client-side only as issued, and update stored values whenever a refresh response arrives
  3. 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

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.