{"record":{"id":"7d200ceb68db2f3f","repo":"dromara/Sa-Token","slug":"30127","errorCode":"30127","errorMessage":"多次请求的 state 不可重复: ","messagePattern":"多次请求的 state 不可重复: ","errorType":"exception","errorClass":"SaOAuth2Exception","httpStatus":null,"severity":"warning","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java","lineNumber":264,"sourceCode":"    @Override\n    public String buildImplicitRedirectUri(String redirectUri, String token, String state) {\n        String url = SaFoxUtil.joinSharpParam(redirectUri, SaOAuth2Consts.Param.token, token);\n        if( ! SaFoxUtil.isEmpty(state)) {\n            checkState(state);\n            url = SaFoxUtil.joinSharpParam(url, SaOAuth2Consts.Param.state, state);\n        }\n        return url;\n    }\n\n    /**\n     * 检查 state 是否被重复使用\n     * @param state /\n     */\n    @Override\n    public void checkState(String state) {\n        String value = SaOAuth2Manager.getDao().getState(state);\n        if(SaFoxUtil.isNotEmpty(value)) {\n            throw new SaOAuth2Exception(\"多次请求的 state 不可重复: \" + state).setCode(SaOAuth2ErrorCode.CODE_30127);\n        }\n        SaOAuth2Manager.getDao().saveState(state);\n    }\n\n}\n\n","sourceCodeStart":246,"sourceCodeEnd":271,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java#L246-L271","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nString state = \"fixed-state\"; // reused every request -> 30127 on 2nd use\nString url = saOAuth2Template.buildAuthorizeUrl(...state...);\n\n// after\nString state = SaFoxUtil.getRandomString(32); // new per request\nsession.setAttribute(\"oauth_state\", state);\nString url = saOAuth2Template.buildAuthorizeUrl(..., state, ...);","handlingStrategy":"validation","validationCode":"String state = SaFoxUtil.getRandomString(32);\nhttpSession.setAttribute(\"oauth_state\", state); // fresh per authorize request\n// on callback: compare returned state to stored one before checkState runs","typeGuard":null,"tryCatchPattern":"catch (SaOAuth2Exception e) {\n    if (e.getCode() == SaOAuth2ErrorCode.CODE_30127) {\n        // restart the flow with a new state; do not retry the same URL\n    }\n}","preventionTips":["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"],"tags":["sa-token","oauth2","csrf","state","replay"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}