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

  1. Re-check the client_secret against the registered SaClientModel (watch for whitespace/newline in config values)
  2. If the secret was rotated, update both the registration and all callers
  3. 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

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.