{"record":{"id":"9e23a651fb284549","repo":"dromara/Sa-Token","slug":"30103","errorCode":"30103","errorMessage":"redirect_uri 不可为空","messagePattern":"redirect_uri 不可为空","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":195,"sourceCode":"\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 + '\\'' +\n\t\t\t\t\", state='\" + state + '\\'' +\n\t\t\t\t\", nonce='\" + nonce + '\\'' +\n\t\t\t\t'}';","sourceCodeStart":177,"sourceCodeEnd":213,"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#L177-L213","documentation":"Thrown by RequestAuthModel.checkModel() when redirectUri is empty. The redirect_uri is mandatory in the authorize flow because the server must send the code/token back to the client. Error code 30103.","triggerScenarios":"Calling /oauth2/authorize with response_type=code but no redirect_uri parameter; manually building RequestAuthModel for token generation without setting redirectUri and calling checkModel().","commonSituations":"Client app configured an incomplete authorize URL; developer assumes redirect_uri registered on the client is enough and omits it from the request; custom password-grant code path reuses checkModel() even though password grant does not need a redirect.","solutions":["Include a redirect_uri in the authorize request that exactly matches one registered in the client's allowUrl list","In custom grant handlers, set a placeholder redirectUri on the RequestAuthModel if the flow does not use redirects, or bypass the full checkModel()","Verify URL encoding of the redirect_uri parameter so it is not lost during transport"],"exampleFix":"// before\nra.clientId = clientId;\nra.loginId = loginId;\nra.scopes = scopes;\nra.checkModel(); // throws 30103\n\n// after\nra.clientId = clientId;\nra.loginId = loginId;\nra.scopes = scopes;\nra.redirectUri = \"http://client.example.com/callback\";\nra.checkModel();","handlingStrategy":"validation","validationCode":"if(SaFoxUtil.isEmpty(redirectUri)) {\n    throw new IllegalArgumentException(\"redirect_uri is required for authorize flow\");\n}","typeGuard":null,"tryCatchPattern":"catch(SaOAuth2Exception e) { if(\"30103\".equals(e.getCode())) return badRequest(\"redirect_uri required\"); }","preventionTips":["Always URL-encode redirect_uri exactly as registered","In non-redirect grant handlers, populate a placeholder redirectUri before checkModel()"],"tags":["oauth2","validation","redirect-uri","sa-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}