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

  1. Send client_id (and client_secret) as request parameters on the token endpoint
  2. Or send an Authorization: Basic base64(client_id:client_secret) header
  3. 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

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.