dromara/Sa-Token · error · SaOAuth2Exception
30191
30191
Error message
请提供 client 信息
What it means
Thrown by SaOAuth2DataResolverDefaultImpl.readClientIdAndSecret() when the request contains neither a Basic Authorization header with client credentials nor a client_id parameter. The resolver supports three input styles: HTTP Basic (client_id:client_secret base64), a single client_id param, or neither — and the last case is rejected. Error code 30191.
Source
Thrown at sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/resolver/SaOAuth2DataResolverDefaultImpl.java:80
// 如果请求参数中没有提供 client_id 参数,则尝试从 Authorization 中获取
String authorizationValue = SaHttpBasicUtil.getAuthorizationValue();
if(SaFoxUtil.isNotEmpty(authorizationValue)) {
String[] arr = authorizationValue.split(":");
clientId = arr[0];
if(arr.length > 1) {
clientSecret = arr[1];
}
return new ClientIdAndSecretModel(clientId, clientSecret);
}
// 如果只提供了 clientId 参数,也为其构建一个 ClientIdAndSecretModel 对象,clientSecret 置空
if(SaFoxUtil.isNotEmpty(clientId)) {
return new ClientIdAndSecretModel(clientId, null);
}
// 如果都没有提供,则抛出异常
throw new SaOAuth2Exception("请提供 client 信息").setCode(SaOAuth2ErrorCode.CODE_30191);
}
/**
* 数据读取:从请求对象中读取 AccessToken,获取不到返回 null,获取不到返回 null
* <br /> 1、请求参数 access_token,2、请求头 Authorization Bearer access_token
*/
@Override
public String readAccessToken(SaRequest request) {
// 优先从请求参数中获取,可以读取到的话直接返回
String accessToken = request.getParam(Param.access_token);
if(SaFoxUtil.isNotEmpty(accessToken)) {
return accessToken;
}
// 如果请求参数中没有提供 access_token 参数,则尝试从 Authorization 中获取
String authorizationValue = request.getHeader(Param.Authorization);
if(SaFoxUtil.isEmpty(authorizationValue)) {
return null;View on GitHub (pinned to ac2c7f6e94)
Solutions
- Send client_id (and client_secret) as request parameters on the token endpoint
- Or send an Authorization: Basic base64(client_id:client_secret) header
- Check proxies/gateways between client and server are not stripping the Authorization header
Example fix
// before curl -X POST http://host/oauth2/token -d 'grant_type=client_credentials' // after curl -X POST http://host/oauth2/token \ -u 1001:aaaa-bbbb-cccc-dddd-eeee \ -d 'grant_type=client_credentials'
Defensive patterns
Strategy: validation
Validate before calling
// client side, before calling token endpoint
String auth = Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes());
httpPost.header("Authorization", "Basic " + auth); Try / catch
catch(SaOAuth2Exception e) { if("30191".equals(e.getCode())) return badRequest("provide client credentials via Basic auth or client_id param"); } Prevention
- Wrap token-endpoint calls in one HTTP client helper that always attaches credentials
- Log outbound Authorization headers (names only) when debugging gateway issues
When it happens
Trigger: Calling /oauth2/token or /oauth2/client_token without client_id in the body/query and without an Authorization: Basic ... header. Also when a custom Authorization header scheme (e.g. Bearer) is sent where Basic was expected.
Common situations: Client switched from Basic auth to body params but dropped client_id entirely; a gateway rewrites or strips the Authorization header; the base64 Basic value is malformed so parsing yields nothing.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/1cba467cf1022ad9.
Report an issue: GitHub.