{"record":{"id":"012861ed3905f8ec","repo":"dromara/Sa-Token","slug":"30102","errorCode":"30102","errorMessage":"scope 不可为空","messagePattern":"scope 不可为空","errorType":"validation","errorClass":"SaOAuth2Exception","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/request/RequestAuthModel.java","lineNumber":192,"sourceCode":"\t/**\n\t * @param nonce 要设置的随机数\n\t * @return 对象自身\n\t */\n\tpublic RequestAuthModel setNonce(String nonce) {\n\t\tthis.nonce = nonce;\n\t\treturn this;\n\t}\n\n\t/**\n\t * 数据自检\n\t * @return 对象自身\n\t */\n\tpublic RequestAuthModel checkModel() {\n\t\tif(SaFoxUtil.isEmpty(clientId)) {\n\t\t\tthrow new SaOAuth2Exception(\"client_id 不可为空\").setCode(SaOAuth2ErrorCode.CODE_30101);\n\t\t}\n\t\tif(SaFoxUtil.isEmpty(scopes)) {\n\t\t\tthrow new SaOAuth2Exception(\"scope 不可为空\").setCode(SaOAuth2ErrorCode.CODE_30102);\n\t\t}\n\t\tif(SaFoxUtil.isEmpty(redirectUri)) {\n\t\t\tthrow new SaOAuth2Exception(\"redirect_uri 不可为空\").setCode(SaOAuth2ErrorCode.CODE_30103);\n\t\t}\n\t\tif(SaFoxUtil.isEmpty(String.valueOf(loginId))) {\n\t\t\tthrow new SaOAuth2Exception(\"LoginId 不可为空\").setCode(SaOAuth2ErrorCode.CODE_30104);\n\t\t}\n\t\treturn this;\n\t}\n\n\t@Override\n\tpublic String toString() {\n\t\treturn \"RequestAuthModel{\" +\n\t\t\t\t\"clientId='\" + clientId + '\\'' +\n\t\t\t\t\", scopes=\" + scopes +\n\t\t\t\t\", loginId=\" + loginId +\n\t\t\t\t\", redirectUri='\" + redirectUri + '\\'' +\n\t\t\t\t\", responseType='\" + responseType + '\\'' +","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/request/RequestAuthModel.java#L174-L210","documentation":"Thrown by RequestAuthModel.checkModel() when the scopes list is empty. sa-token requires every authorization request to carry at least one scope; this is the second self-check performed on the RequestAuthModel. Error code 30102.","triggerScenarios":"Calling /oauth2/authorize without the scope parameter, or passing an empty scope string (e.g. scope=) which converts to an empty list. Custom code that constructs RequestAuthModel without assigning ra.scopes and then generates a token.","commonSituations":"Client app does not send scope because it assumes a default; integration code copies an example URL that omitted scope; scope string parsing returns empty after trimming separators.","solutions":["Add a scope parameter with at least one value that the client has contracted, e.g. scope=getuserinfo","If a default scope is desired, set it server-side before checkModel() runs (e.g. in a custom grant-type handler or authorize pre-processing)","Verify the registered SaClientModel contractScopes includes the requested scope so the next check (30112) does not fail"],"exampleFix":"// before\nra.scopes = scopes; // scopes may be null\n\n// after\nra.scopes = (scopes == null || scopes.isEmpty()) ? Arrays.asList(\"getuserinfo\") : scopes;\nra.checkModel();","handlingStrategy":"validation","validationCode":"List<String> scopes = converter.convertScopeStringToList(scopeParam);\nif(scopes == null || scopes.isEmpty()) {\n    throw new IllegalArgumentException(\"at least one scope is required\");\n}","typeGuard":null,"tryCatchPattern":"catch(SaOAuth2Exception e) { if(\"30102\".equals(e.getCode())) return badRequest(\"scope required\"); }","preventionTips":["Define default scopes per client and inject them before calling checkModel()","Keep a shared scope vocabulary between client app and client registration"],"tags":["oauth2","validation","scope","sa-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}