dromara/Sa-Token · error · SaOAuth2ClientModelException
30115
30115
Error message
无效 client_secret:
What it means
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.
Source
Thrown at sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java:77
SaClientModel clientModel = getClientModel(clientId);
if(clientModel == null) {
throw new SaOAuth2ClientModelException("无效 client_id: " + clientId)
.setClientId(clientId)
.setCode(SaOAuth2ErrorCode.CODE_30105);
}
return clientModel;
}
/**
* 校验:clientId 与 clientSecret 是否正确,正确返回 SaClientModel,不正确抛出异常
* @param clientId 应用id
* @param clientSecret 秘钥
* @return SaClientModel对象
*/
public SaClientModel checkClientSecret(String clientId, String clientSecret) {
SaClientModel cm = checkClientModel(clientId);
if(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret)) {
throw new SaOAuth2ClientModelException("无效 client_secret: " + clientSecret)
.setClientId(clientId)
.setCode(SaOAuth2ErrorCode.CODE_30115);
}
return cm;
}
/**
* 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限
* @return SaClientModel对象
*/
public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List<String> scopes) {
SaClientModel cm = checkClientSecret(clientId, clientSecret);
checkContractScope(cm, scopes);
return cm;
}View on GitHub (pinned to ac2c7f6e94)
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
Example fix
# before # registered secret: 'aaaa-bbbb' but request sends old value curl -u 1001:old-secret ... # after curl -u 1001:aaaa-bbbb-cccc-dddd-eeee ...
Defensive patterns
Strategy: try-catch
Validate before calling
// client side: verify secret is loaded and non-empty before calling
if(clientSecret == null || clientSecret.trim().isEmpty()) {
throw new IllegalStateException("client_secret not configured");
} Try / catch
catch(SaOAuth2ClientModelException e) {
if("30115".equals(e.getCode())) return 401 "invalid client_secret"; // never log the secret value
} Prevention
- Store secrets in a config/secret manager; trim values on load
- Rotate secrets on both sides atomically and keep a brief overlap window
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/99c51f2b5fb4c202.
Report an issue: GitHub.