{"record":{"id":"6f73ba5618835e22","repo":"dromara/Sa-Token","slug":"code-30001","errorCode":"CODE_30001","errorMessage":"无效redirect：{url}","messagePattern":"无效redirect：(.+?)","errorType":"exception","errorClass":"SaSsoException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java","lineNumber":416,"sourceCode":"        String back = url.substring(index + length);\n        back = SaFoxUtil.encodeUrl(back);\n\n        // 放回url中\n        url = url.substring(0, index + length) + back;\n        return url;\n    }\n\n    /**\n     * 校验重定向 url 合法性\n     *\n     * @param client 应用标识\n     * @param url 下放ticket的url地址\n     */\n    public void checkRedirectUrl(String client, String url) {\n\n        // 1、是否是一个有效的url\n        if( ! SaFoxUtil.isUrl(url) ) {\n            throw new SaSsoException(\"无效redirect：\" + url).setCode(SaSsoErrorCode.CODE_30001);\n        }\n\n        // 2、截取掉?后面的部分\n        int qIndex = url.indexOf(\"?\");\n        if(qIndex != -1) {\n            url = url.substring(0, qIndex);\n        }\n\n        // 3、不允许出现@字符\n        if(url.contains(\"@\")) {\n            //  为什么不允许出现 @ 字符呢，因为这有可能导致 redirect 参数绕过 AllowUrl 列表的校验\n            //\n            //  举个例子 配置文件：\n            //       sa-token.sso-server.allow-url=http://sa-sso-client1.com*\n            //\n            //  开发者原意是为了允许 sa-sso-client1.com 下的所有地址都可以下放ticket\n            //\n            //  但是如果攻击者精心构建一个url：","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java#L398-L434","documentation":"First-stage rejection in checkRedirectUrl: the redirect value fails SaFoxUtil.isUrl(url), i.e. it is not a well-formed URL at all (no scheme, malformed host). The server validates redirect targets before handing out tickets to prevent open-redirect abuse.","triggerScenarios":"/sso/auth (mode=simple) is called with a redirect parameter like '/back' or '127.0.0.1/callback' — anything that is not a parseable absolute URL.","commonSituations":"Client builds the redirect from a relative path; reverse proxy rewrites the query and truncates the scheme; URL-encoding bugs mangle the parameter (e.g. http%3A%2F%2F decoded twice or not at all).","solutions":["Send a fully-qualified URL as redirect, e.g. http://client.com/sso/login","URL-encode the redirect parameter exactly once when embedding it in the auth URL","Check proxy/gateway rewrite rules are not stripping the scheme from the query string"],"exampleFix":"// before (client)\nString authUrl = server + \"/sso/auth?redirect=/sso/login\";\n\n// after\nString authUrl = server + \"/sso/auth?redirect=\" + URLEncoder.encode(\"http://client.com/sso/login\", \"UTF-8\");","handlingStrategy":"validation","validationCode":"if(!SaFoxUtil.isUrl(redirect)) {\n    // reject / rebuild the redirect as an absolute URL before sending to /sso/auth\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always send absolute, once-encoded redirect URLs","Test the generated auth URL by pasting it into a browser during integration"],"tags":["sa-token","sso","redirect","validation","security"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}