{"record":{"id":"2d1c331e0684a79c","repo":"dromara/Sa-Token","slug":"30113","errorCode":"30113","errorMessage":"无效 redirect_url：","messagePattern":"无效 redirect_url：","errorType":"exception","errorClass":"SaOAuth2ClientModelException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java","lineNumber":153,"sourceCode":"\t\t\t\t\t\t.setClientId(cm.clientId)\n\t\t\t\t\t\t.setScope(scope)\n\t\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30112);\n\t\t\t}\n\t\t}\n\t\treturn cm;\n\t}\n\n\t// --------- redirect_uri 相关\n\n\t/**\n\t * 校验：该 Client 使用指定 url 作为回调地址，是否合法\n\t * @param clientId 应用id\n\t * @param url 指定url\n\t */\n\tpublic void checkRedirectUri(String clientId, String url) {\n\t\t// 1、是否是一个有效的url\n\t\tif( ! SaFoxUtil.isUrl(url)) {\n\t\t\tthrow new SaOAuth2ClientModelException(\"无效 redirect_url：\" + url)\n\t\t\t\t\t.setClientId(clientId)\n\t\t\t\t\t.setCode(SaOAuth2ErrorCode.CODE_30113);\n\t\t}\n\n\t\t// 2、截取掉?后面的部分\n\t\tint qIndex = url.indexOf(\"?\");\n\t\tif(qIndex != -1) {\n\t\t\turl = url.substring(0, qIndex);\n\t\t}\n\n\t\t// 3、不允许出现@字符\n\t\tif(url.contains(\"@\")) {\n\t\t\t//  为什么不允许出现 @ 字符呢，因为这有可能导致 redirect_url 参数绕过 AllowUrl 列表的校验\n\t\t\t//\n\t\t\t//  举个例子 SaClientModel 配置：\n\t\t\t//       allow-url=http://sa-oauth-client.com*\n\t\t\t//\n\t\t\t//  开发者原意是为了允许 sa-oauth-client.com 下的所有地址都可以下放 code","sourceCodeStart":135,"sourceCodeEnd":171,"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#L135-L171","documentation":"Thrown by SaOAuth2Template.checkRedirectUri when the redirect_uri passed to the OAuth2 authorize endpoint is not a syntactically valid URL (SaFoxUtil.isUrl fails). The library validates the callback URL before issuing an authorization code, because the redirect_uri is where the code gets delivered; an unparseable value cannot be trusted or matched. Error code is 30113 and the offending URL is appended to the message.","triggerScenarios":"Calling the /oauth2/authorize endpoint with a missing, empty, or malformed redirect_uri parameter (e.g. 'redirect_uri=abc', a relative path, or a value without scheme) for a registered client.","commonSituations":"Front-end builds the authorize link by hand and URL-encodes the redirect_uri incorrectly, or omits it entirely; a proxy/gateway strips query parameters; environment config differences between test and prod supply an incomplete URL.","solutions":["Ensure redirect_uri is an absolute URL with scheme and host, e.g. http://client.example.com/callback","URL-encode the redirect_uri when constructing the authorize link so '&' or '=' inside it survive transport","Verify no gateway/filter strips or rewrites the redirect_uri request parameter before it reaches the OAuth2 module"],"exampleFix":"// before\nString url = server + \"/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=\" + callback;\n// after\nString url = server + \"/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=\" + URLEncoder.encode(callback, \"UTF-8\");","handlingStrategy":"validation","validationCode":"boolean ok = redirectUri != null && java.util.regex.Pattern.matches(\"^https?://[\\\\w.-]+(:\\\\d+)?(/.*)?$\", redirectUri);\nif (!ok) throw new IllegalArgumentException(\"redirect_uri must be an absolute http(s) URL: \" + redirectUri);","typeGuard":null,"tryCatchPattern":"try { saOAuth2Template.checkRedirectUri(clientId, url); } catch (SaOAuth2ClientModelException e) { if (e.getCode() == 30113) return badRequest(\"invalid redirect_uri\"); throw e; }","preventionTips":["Centralize authorize-URL construction in one helper that always URL-encodes redirect_uri","Validate redirect_uri format on the client before navigating to the authorize endpoint"],"tags":["oauth2","redirect-uri","validation","url"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}