{"record":{"id":"99c51f2b5fb4c202","repo":"dromara/Sa-Token","slug":"30115","errorCode":"30115","errorMessage":"无效 client_secret: ","messagePattern":"无效 client_secret: ","errorType":"exception","errorClass":"SaOAuth2ClientModelException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java","lineNumber":77,"sourceCode":"\t\tSaClientModel clientModel = getClientModel(clientId);\n\t\tif(clientModel == null) {\n\t\t\tthrow new SaOAuth2ClientModelException(\"无效 client_id: \" + clientId)\n\t\t\t\t\t.setClientId(clientId)\n\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30105);\n\t\t}\n\t\treturn clientModel;\n\t}\n\n\t/**\n\t * 校验：clientId 与 clientSecret 是否正确，正确返回 SaClientModel，不正确抛出异常\n\t * @param clientId 应用id\n\t * @param clientSecret 秘钥\n\t * @return SaClientModel对象\n\t */\n\tpublic SaClientModel checkClientSecret(String clientId, String clientSecret) {\n\t\tSaClientModel cm = checkClientModel(clientId);\n\t\tif(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret)) {\n\t\t\tthrow new SaOAuth2ClientModelException(\"无效 client_secret: \" + clientSecret)\n\t\t\t\t\t.setClientId(clientId)\n\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30115);\n\t\t}\n\t\treturn cm;\n\t}\n\n\t/**\n\t * 校验：clientId 与 clientSecret 是否正确，并且是否签约了指定 scopes\n\t * @param clientId 应用id\n\t * @param clientSecret 秘钥\n\t * @param scopes 权限\n\t * @return SaClientModel对象\n\t */\n\tpublic SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {\n\t\tSaClientModel cm = checkClientSecret(clientId, clientSecret);\n\t\tcheckContractScope(cm, scopes);\n\t\treturn cm;\n\t}","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java#L59-L95","documentation":"Thrown by SaOAuth2Template.checkClientSecret: the client exists but the provided client_secret does not match (or the stored secret is null). Comparison is a plain equals against SaClientModel.clientSecret. Thrown as SaOAuth2ClientModelException with the clientId attached. Error code 30115.","triggerScenarios":"Token/client_token requests where the Basic header secret or client_secret param is wrong, has trailing whitespace, or the registered secret was regenerated; also when the stored SaClientModel was built without setting clientSecret.","commonSituations":"Secret rotated on one side only; secrets copied with an invisible newline from config files; Basic header not base64-encoded correctly; environment-specific secrets mixed up.","solutions":["Re-check the client_secret against the registered SaClientModel (watch for whitespace/newline in config values)","If the secret was rotated, update both the registration and all callers","Ensure the Basic Authorization header is base64(client_id:client_secret) exactly"],"exampleFix":"# before\n# registered secret: 'aaaa-bbbb' but request sends old value\ncurl -u 1001:old-secret ...\n\n# after\ncurl -u 1001:aaaa-bbbb-cccc-dddd-eeee ...","handlingStrategy":"try-catch","validationCode":"// client side: verify secret is loaded and non-empty before calling\nif(clientSecret == null || clientSecret.trim().isEmpty()) {\n    throw new IllegalStateException(\"client_secret not configured\");\n}","typeGuard":null,"tryCatchPattern":"catch(SaOAuth2ClientModelException e) {\n    if(\"30115\".equals(e.getCode())) return 401 \"invalid client_secret\"; // never log the secret value\n}","preventionTips":["Store secrets in a config/secret manager; trim values on load","Rotate secrets on both sides atomically and keep a brief overlap window"],"tags":["oauth2","client-secret","credentials","sa-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}