{"record":{"id":"01416eb869ffc6d2","repo":"dromara/Sa-Token","slug":"30109","errorCode":"30109","errorMessage":"该 client_token 不具备 scope：","messagePattern":"该 client_token 不具备 scope：","errorType":"exception","errorClass":"SaOAuth2ClientTokenScopeException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java","lineNumber":685,"sourceCode":"\t\t\treturn true;\n\t\t} catch (SaOAuth2ClientTokenException e) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * 校验：指定 Client-Token 是否具有指定 Scope 列表，如果不具备则抛出异常\n\t * @param clientToken Client-Token\n\t * @param scopes 需要校验的权限列表\n\t */\n\tpublic void checkClientTokenScope(String clientToken, String... scopes) {\n\t\tClientTokenModel ct = checkClientToken(clientToken);\n\t\tif(SaFoxUtil.isEmptyArray(scopes)) {\n\t\t\treturn;\n\t\t}\n\t\tfor (String scope : scopes) {\n\t\t\tif(! ct.scopes.contains(scope)) {\n\t\t\t\tthrow new SaOAuth2ClientTokenScopeException(\"该 client_token 不具备 scope：\" + scope)\n\t\t\t\t\t\t.setClientToken(clientToken)\n\t\t\t\t\t\t.setScope(scope)\n\t\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30109);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 回收一个 ClientToken\n\t *\n\t * @param clientToken /\n\t */\n\tpublic void revokeClientToken(String clientToken) {\n\t\tClientTokenModel ct = getClientToken(clientToken);\n\t\tif(ct == null) {\n\t\t\treturn;\n\t\t}\n\t\t// 删 ct、删索引","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java#L667-L703","documentation":"Thrown by checkClientTokenScope when a valid client token lacks one of the enforced scopes (code 30109, SaOAuth2ClientTokenScopeException). Analogous to the access-token scope check but for client_credentials tokens: the token authenticates the app, but the app was not granted the specific scope the API demands.","triggerScenarios":"An API validates a client token with SaOAuth2Util.checkClientToken(token, \"pay\") but the client_credentials token request omitted scope=pay or the server's client config does not allow it.","commonSituations":"New downstream API requires a scope the caller never requests; server-side allow-scope for the client is missing entries; scope spelling/separator mismatch between grant and check.","solutions":["Add the required scope to the client_credentials token request body (scope=pay,order)","Extend the client's allow-scope configuration on the OAuth2 server","Align scope naming/separators between the issuing request and the enforcement check"],"exampleFix":"// before\nMap<String,Object> p = new HashMap<>(); p.put(\"grant_type\",\"client_credentials\"); p.put(\"client_id\",\"1001\"); p.put(\"client_secret\",\"xx\");\n// after\np.put(\"scope\",\"pay,order\"); // request the scopes the API will check","handlingStrategy":"try-catch","validationCode":"ClientTokenModel ct = saOAuth2Template.getClientToken(token);\nboolean ok = ct != null && java.util.Arrays.asList(scopes).stream().allMatch(s -> ct.scopes.contains(s));","typeGuard":null,"tryCatchPattern":"try { saOAuth2Template.checkClientTokenScope(token, \"pay\"); } catch (SaOAuth2ClientTokenScopeException e) { return status(403, \"client lacks scope: \" + e.getScope()); }","preventionTips":["Align the client_credentials scope request with every API the integration calls","Add scope requirements to the API registry so new endpoints update the grant automatically"],"tags":["oauth2","scope","client-token","authorization"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}