dromara/Sa-Token · error · SaSsoException

CODE_30002

CODE_30002

Error message

非法redirect:{url}

What it means

Final-stage rejection in checkRedirectUrl: the URL is well-formed and '@'-free but does not match any entry in the client's configured allow-url list. The server only issues tickets to pre-registered callback addresses to prevent ticket leakage to arbitrary hosts.

Source

Thrown at sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerTemplate.java:455

            //  那么这个url就会绕过 allow-url 的校验,ticket 被下发到了第三方服务器地址:
            //       http://sa-token.com/?ticket=i8vDfbpqBViMe01QoLY1kHROJWYvv9plBtvTZ6kk77KK0e0U4Xj99NPfSZEYjRul
            //
            //  造成了ticket 参数劫持
            //  所以此处需要禁止在 url 中出现 @ 字符
            //
            //  这么一刀切的做法,可能会导致一些特殊的正常url也无法通过校验,例如:
            //       http://sa-sso-server.com:9000/sso/auth?redirect=http://sa-sso-client1.com:9003/@getInfo
            //
            //  但是为了安全起见,这么做还是有必要的
            throw new SaSsoException("无效redirect(不允许出现@字符):" + url).setCode(SaSsoErrorCode.CODE_30001);
        }

        // 4、判断是否在 [ 允许的地址列表 ] 之中
        String allowUrlString = getClientNotNull(client).getAllowUrl();
        List<String> allowUrlList = Arrays.asList(allowUrlString.replaceAll(" ", "").split(","));
        checkAllowUrlList(allowUrlList);
        if( ! SaStrategy.instance.hasElement.apply(allowUrlList, url) ) {
            throw new SaSsoException("非法redirect:" + url).setCode(SaSsoErrorCode.CODE_30002);
        }

        // 校验通过 √
    }

    /**
     * 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常
     * @param allowUrlList 待校验的 allow-url 地址列表 
     */
    public void checkAllowUrlList(List<String> allowUrlList){
        checkAllowUrlListStaticMethod(allowUrlList);
    }

    /**
     * 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常
     * @param allowUrlList 待校验的 allow-url 地址列表
     */
    public static void checkAllowUrlListStaticMethod(List<String> allowUrlList){

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Add the exact redirect origin to the client's allow-url on the sso-server, e.g. http://client.com/sso/login or http://client.com/*
  2. Match the port and path exactly — http://client.com and http://client.com:8080 are different entries
  3. Use a trailing * only at the end (e.g. http://client.com/*) for path-level wildcards

Example fix

# application.yml (sso-server)
# before
sa-token:
  sso:
    server:
      clients:
        - client: app1
          allow-url: http://old-domain.com/sso/login

# after
sa-token:
  sso:
    server:
      clients:
        - client: app1
          allow-url: http://old-domain.com/sso/login,http://new-domain.com:8080/sso/login
Defensive patterns

Strategy: validation

Validate before calling

List<String> allow = Arrays.asList(scm.getAllowUrl().replaceAll(" ", "").split(","));
if(!SaStrategy.instance.hasElement.apply(allow, redirect)) {
    // fail with your own message listing the allowed origins
}

Prevention

When it happens

Trigger: redirect=http://newhost.com/sso/login while the client's allow-url config contains only other domains; the URL differs from the allowed entry by port, path, or trailing slash; allow-url uses '*' in a way that does not actually cover this URL.

Common situations: New deployment on a different domain/port forgotten in allow-url; allow-url copied with a typo; subdomain not covered because the pattern ends at the parent domain; spaces in the comma list (they are stripped, but verify).

Related errors


AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14). Data as JSON: /api/errors/1c83241c27cea8d8. Report an issue: GitHub.