{"record":{"id":"df5454924ba24264","repo":"wuyouzhuguli/SpringAll","slug":"clientsecret","errorCode":null,"errorMessage":"clientSecret不正确","messagePattern":"clientSecret不正确","errorType":"exception","errorClass":"UnapprovedClientAuthenticationException","httpStatus":null,"severity":"error","filePath":"64.Spring-Security-OAuth2-Customize/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java","lineNumber":55,"sourceCode":"        String header = request.getHeader(\"Authorization\");\n        if (header == null || !header.startsWith(\"Basic \")) {\n            throw new UnapprovedClientAuthenticationException(\"请求头中无client信息\");\n        }\n\n        String[] tokens = this.extractAndDecodeHeader(header, request);\n        String clientId = tokens[0];\n        String clientSecret = tokens[1];\n\n        TokenRequest tokenRequest = null;\n\n        // 2. 通过 ClientDetailsService 获取 ClientDetails\n        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);\n\n        // 3. 校验 ClientId和 ClientSecret的正确性\n        if (clientDetails == null) {\n            throw new UnapprovedClientAuthenticationException(\"clientId:\" + clientId + \"对应的信息不存在\");\n        } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {\n            throw new UnapprovedClientAuthenticationException(\"clientSecret不正确\");\n        } else {\n            // 4. 通过 TokenRequest构造器生成 TokenRequest\n            tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), \"custom\");\n        }\n\n        // 5. 通过 TokenRequest的 createOAuth2Request方法获取 OAuth2Request\n        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);\n        // 6. 通过 Authentication和 OAuth2Request构造出 OAuth2Authentication\n        OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);\n\n        // 7. 通过 AuthorizationServerTokenServices 生成 OAuth2AccessToken\n        OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(auth2Authentication);\n\n        // 8. 返回 Token\n        log.info(\"登录成功\");\n        response.setContentType(\"application/json;charset=UTF-8\");\n        response.getWriter().write(new ObjectMapper().writeValueAsString(token));\n    }","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/wuyouzhuguli/SpringAll/blob/614d2578d9495acf53cc02f2dee9c6131cc5e51a/64.Spring-Security-OAuth2-Customize/src/main/java/cc/mrbird/security/handler/MyAuthenticationSucessHandler.java#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Better: replace the plain equals with a PasswordEncoder.matches check (e.g. passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) so encoded secrets work.","Trim whitespace from the decoded clientId/secret in extractAndDecodeHeader to avoid trailing-newline mismatches."],"exampleFix":"// before\n// } else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {\n//     throw new UnapprovedClientAuthenticationException(\"clientSecret不正确\");\n// }\n\n// after\n} else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {\n    throw new UnapprovedClientAuthenticationException(\"clientSecret不正确\");\n}","handlingStrategy":"validation","validationCode":"// Reconcile secret representation before relying on the server's plain compare.\n// If the store keeps raw secrets, send the raw secret:\nconst secret = RAW_SECRET; // not a hash\nconst header = 'Basic ' + btoa(`${clientId}:${secret}`);","typeGuard":null,"tryCatchPattern":"try { await login(); }\ncatch (e) {\n  if (/clientSecret不正确/.test(e.message)) { /* verify stored encoding; use PasswordEncoder.matches on server */ }\n  else handleError(e);\n}","preventionTips":["Replace the plain StringUtils.equals with PasswordEncoder.matches so encoded secrets work.","Keep one source of truth for client secrets.","Trim decoded secrets to avoid newline/whitespace drift."],"tags":["oauth2","security","client","password-encoding","spring-security"],"backgroundTag":null,"analyzedSha":"614d2578d9495acf53cc02f2dee9c6131cc5e51a","analyzedAt":"2026-08-14T04:40:03.488Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}