{"record":{"id":"33921d7551ad8177","repo":"dromara/Sa-Token","slug":"30104","errorCode":"30104","errorMessage":"LoginId 不可为空","messagePattern":"LoginId 不可为空","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":198,"sourceCode":"\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'}';\n\t}\n\n}","sourceCodeStart":180,"sourceCodeEnd":216,"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#L180-L216","documentation":"Thrown by RequestAuthModel.checkModel() when loginId is empty. loginId identifies the user on whose behalf the token is issued; note the check uses String.valueOf(loginId), so a literal empty-string loginId triggers it while a null loginId becomes the string \"null\" and passes this particular check. Error code 30104.","triggerScenarios":"Authorize flow reached without a logged-in session, so the loginId put into RequestAuthModel is an empty string; custom code that sets ra.loginId = \"\" before checkModel().","commonSituations":"User's session expired or was kicked before confirming authorization; StpLogic.getLoginId() returned an empty value that was copied into the model; login callback did not actually perform StpUtil.login().","solutions":["Ensure the user is logged in before the authorize/confirm step (check StpUtil.isLogin() and redirect to login if not)","Verify StpUtil.login(userId) was actually called in your login endpoint","If building RequestAuthModel manually, always assign a non-empty loginId from your auth result"],"exampleFix":"// before\nObject loginId = StpUtil.getLoginIdDefaultNull(); // may be null/empty after session expiry\nra.loginId = loginId;\n\n// after\nif(!StpUtil.isLogin()) { return res.redirect(\"/login?back=\" + currUrl); }\nra.loginId = StpUtil.getLoginId();","handlingStrategy":"validation","validationCode":"Object loginId = StpUtil.getLoginIdDefaultNull();\nif(loginId == null || \"\".equals(String.valueOf(loginId))) {\n    // force re-login instead of proceeding\n    return res.redirect(loginPage + \"?back=\" + currUrl);\n}","typeGuard":null,"tryCatchPattern":"catch(SaOAuth2Exception e) { if(\"30104\".equals(e.getCode())) redirect to login; }","preventionTips":["Guard every OAuth2 page behind StpUtil.isLogin() in your auth filter","Never pass String.valueOf(null) results into RequestAuthModel — resolve loginId explicitly"],"tags":["oauth2","validation","login-id","session","sa-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}