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

  1. Generate a fresh random state for every authorize request (never hard-code or reuse)
  2. On the client, handle 30127 by restarting the OAuth2 flow with a new state instead of retrying the same URL
  3. 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

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.