{"record":{"id":"ad042a6937889c7d","repo":"dromara/Sa-Token","slug":"30107","errorCode":"30107","errorMessage":"无效 client_token: ","messagePattern":"无效 client_token: ","errorType":"exception","errorClass":"SaOAuth2ClientTokenException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java","lineNumber":642,"sourceCode":"\n\t/**\n\t * 获取 ClientTokenModel，无效的 ClientToken 会返回 null\n\t * @param clientToken /\n\t * @return /\n\t */\n\tpublic ClientTokenModel getClientToken(String clientToken) {\n\t\treturn SaOAuth2Manager.getDao().getClientToken(clientToken);\n\t}\n\n\t/**\n\t * 校验 Client-Token，成功返回 ClientTokenModel，失败则抛出异常\n\t * @param clientToken /\n\t * @return /\n\t */\n\tpublic ClientTokenModel checkClientToken(String clientToken) {\n\t\tClientTokenModel ct = getClientToken(clientToken);\n\t\tif(ct == null) {\n\t\t\tthrow new SaOAuth2ClientTokenException(\"无效 client_token: \" + clientToken)\n\t\t\t\t\t.setClientToken(clientToken)\n\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30107);\n\t\t}\n\t\treturn ct;\n\t}\n\n\t/**\n\t * 获取 Client-Token 列表：此应用下 对 某个用户 签发的所有 Client-token\n\t *\n\t * @param clientId /\n\t * @return /\n\t */\n\tpublic List<String> getClientTokenValueList(String clientId) {\n\t\treturn SaOAuth2Manager.getDao().getClientTokenValueList_FromAdjustAfter(clientId);\n\t}\n\n\t/**\n\t * 判断：指定 Client-Token 是否具有指定 Scope 列表，返回 true 或 false","sourceCodeStart":624,"sourceCodeEnd":660,"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#L624-L660","documentation":"Thrown by SaOAuth2Template.checkClientToken when the client token lookup returns null (code 30107, SaOAuth2ClientTokenException). Client tokens are issued via the client_credentials grant for application-to-application calls; a miss means unknown, expired, or revoked token.","triggerScenarios":"Calling /oauth2/token?grant_type=client_credentials succeeds then the token expires, or an API protected by checkClientToken receives a missing/garbled/fabricated clientToken parameter.","commonSituations":"Client-token timeout is short and the calling service caches the token beyond expiry; token not propagated through a gateway; in-memory DAO lost on restart; caller sends access token where client token is required.","solutions":["Request a new client token via the client_credentials grant before retrying","Cache the client token with its expires-in and refresh it before expiry instead of hardcoding","Confirm the parameter name/header the server expects (clientToken / satoken) and share the DAO across nodes"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (saOAuth2Template.getClientToken(cachedToken) == null) { cachedToken = requestNewClientToken(); }","typeGuard":null,"tryCatchPattern":"try { saOAuth2Template.checkClientToken(token); } catch (SaOAuth2ClientTokenException e) { token = requestClientCredentialsToken(); retryOnce(request); }","preventionTips":["Cache client tokens with their TTL and refresh ahead of expiry","Never hardcode a client token in configuration"],"tags":["oauth2","client-token","validation","token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}