wuyouzhuguli/SpringAll · error · UnapprovedClientAuthenticationException

clientSecret不正确

Error message

clientSecret不正确

What it means

UnapprovedClientAuthenticationException thrown when clientDetails.getClientSecret() does not equal the secret decoded from the Basic header. The check is a plain StringUtils.equals on raw strings — NO password encoder is applied — so if the stored secret is BCrypt/NoOp-encoded this comparison will fail. This is a deliberate simplification in the sample but a real footgun.

Source

Thrown at 64.Spring-Security-OAuth2-Customize/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java:55

        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        String[] tokens = this.extractAndDecodeHeader(header, request);
        String clientId = tokens[0];
        String clientSecret = tokens[1];

        TokenRequest tokenRequest = null;

        // 2. 通过 ClientDetailsService 获取 ClientDetails
        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        // 3. 校验 ClientId和 ClientSecret的正确性
        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "对应的信息不存在");
        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
            throw new UnapprovedClientAuthenticationException("clientSecret不正确");
        } else {
            // 4. 通过 TokenRequest构造器生成 TokenRequest
            tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
        }

        // 5. 通过 TokenRequest的 createOAuth2Request方法获取 OAuth2Request
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        // 6. 通过 Authentication和 OAuth2Request构造出 OAuth2Authentication
        OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

        // 7. 通过 AuthorizationServerTokenServices 生成 OAuth2AccessToken
        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(auth2Authentication);

        // 8. 返回 Token
        log.info("登录成功");
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(token));
    }

View on GitHub (pinned to 614d2578d9)

Solutions

  1. Make the submitted secret match the stored value exactly as the comparison expects — for this sample's plain StringUtils.equals, store and send the raw secret.
  2. Better: replace the plain equals with a PasswordEncoder.matches check (e.g. passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) so encoded secrets work.
  3. Trim whitespace from the decoded clientId/secret in extractAndDecodeHeader to avoid trailing-newline mismatches.

Example fix

// before
// } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {
//     throw new UnapprovedClientAuthenticationException("clientSecret不正确");
// }

// after
} else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
    throw new UnapprovedClientAuthenticationException("clientSecret不正确");
}
Defensive patterns

Strategy: validation

Validate before calling

// Reconcile secret representation before relying on the server's plain compare.
// If the store keeps raw secrets, send the raw secret:
const secret = RAW_SECRET; // not a hash
const header = 'Basic ' + btoa(`${clientId}:${secret}`);

Try / catch

try { await login(); }
catch (e) {
  if (/clientSecret不正确/.test(e.message)) { /* verify stored encoding; use PasswordEncoder.matches on server */ }
  else handleError(e);
}

Prevention

When it happens

Trigger: Submitted clientSecret does not match the stored value byte-for-byte; stored secret is encoded (e.g. '{bcrypt}...' or a BCrypt hash) but compared as plaintext; secret was rotated.

Common situations: Spring Security OAuth2 stores client secrets encoded (DelegatingPasswordEncoder), so StringUtils.equals against the encoded string always fails; copying a secret from docs that was already hashed; whitespace/newline in the decoded secret.

Related errors


AI-assisted analysis of wuyouzhuguli/SpringAll@614d2578d9 (2026-08-14). Data as JSON: /api/errors/df5454924ba24264. Report an issue: GitHub.