dromara/Sa-Token · warning · SaOAuth2Exception
30127
30127
Error message
多次请求的 state 不可重复:
What it means
Thrown by SaOAuth2DataGenerateDefaultImpl.checkState when the state parameter of an OAuth2 authorize request is found already stored in the dao. Sa-Token enforces single-use state to block CSRF/replay on the authorization-code flow; code 30127 marks state reuse.
Source
Thrown at sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java:264
@Override
public String buildImplicitRedirectUri(String redirectUri, String token, String state) {
String url = SaFoxUtil.joinSharpParam(redirectUri, SaOAuth2Consts.Param.token, token);
if( ! SaFoxUtil.isEmpty(state)) {
checkState(state);
url = SaFoxUtil.joinSharpParam(url, SaOAuth2Consts.Param.state, state);
}
return url;
}
/**
* 检查 state 是否被重复使用
* @param state /
*/
@Override
public void checkState(String state) {
String value = SaOAuth2Manager.getDao().getState(state);
if(SaFoxUtil.isNotEmpty(value)) {
throw new SaOAuth2Exception("多次请求的 state 不可重复: " + state).setCode(SaOAuth2ErrorCode.CODE_30127);
}
SaOAuth2Manager.getDao().saveState(state);
}
}
View on GitHub (pinned to ac2c7f6e94)
Solutions
- Generate a fresh random state for every authorize request (never hard-code or reuse)
- On the client, handle 30127 by restarting the OAuth2 flow with a new state instead of retrying the same URL
- If using an in-memory dao, note that restarts clear used states — for production use Redis so replay protection actually persists
Example fix
// before
String state = "fixed-state"; // reused every request -> 30127 on 2nd use
String url = saOAuth2Template.buildAuthorizeUrl(...state...);
// after
String state = SaFoxUtil.getRandomString(32); // new per request
session.setAttribute("oauth_state", state);
String url = saOAuth2Template.buildAuthorizeUrl(..., state, ...); Defensive patterns
Strategy: validation
Validate before calling
String state = SaFoxUtil.getRandomString(32);
httpSession.setAttribute("oauth_state", state); // fresh per authorize request
// on callback: compare returned state to stored one before checkState runs Try / catch
catch (SaOAuth2Exception e) {
if (e.getCode() == SaOAuth2ErrorCode.CODE_30127) {
// restart the flow with a new state; do not retry the same URL
}
} Prevention
- Never hard-code or cache state values; generate per request
- On refresh/back-button flows, generate a new authorize URL with new state
- Use Redis dao in production so used states persist
When it happens
Trigger: The authorization endpoint (or checkState called directly) receives a state value that was already used once — e.g. the user refreshes the authorize page, or the same authorize URL is replayed/intercepted.
Common situations: Browser back/refresh resubmitting the authorize request with the same state; a hard-coded state string in tests or misconfigured clients; duplicate submission by aggressive prefetchers; legitimate double-clicks on the authorize button.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/7d200ceb68db2f3f.
Report an issue: GitHub.