dromara/Sa-Token · error · SaSsoException

CODE_30004

CODE_30004

Error message

无效 ticket : {ticket}

What it means

Thrown by SaSsoServerTemplate.checkTicket when getTicket(ticket) returns null — the ticket string does not exist in the server's storage. SSO tickets are short-lived, one-time-use random strings created at /sso/auth; a lookup miss means the ticket was never issued, has been consumed, or has expired.

Source

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

     * @param cs 要转换的类型
     * @return 账号id
     */
    public <T> T getLoginId(String ticket, Class<T> cs) {
        return SaFoxUtil.getValueByType(getLoginId(ticket), cs);
    }

    // 校验

    /**
     * 校验 Ticket,无效 ticket 会抛出异常
     *
     * @param ticket Ticket码
     * @return /
     */
    public TicketModel checkTicket(String ticket) {
        TicketModel ticketModel = getTicket(ticket);
        if(ticketModel == null) {
            throw new SaSsoException("无效 ticket : " + ticket).setCode(SaSsoErrorCode.CODE_30004);
        }
        return ticketModel;
    }

    /**
     * 校验 Ticket 码,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除
     * @param ticket Ticket码
     * @return 账号id
     */
    public TicketModel checkTicketParamAndDelete(String ticket) {
        return checkTicketParamAndDelete(ticket, SaSsoConsts.CLIENT_WILDCARD);
    }

    /**
     * 校验 Ticket,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除
     *
     * @param ticket Ticket码
     * @param client client 标识

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Redirect the user back to the SSO login/auth flow instead of showing a raw error — the ticket is gone and must be reissued
  2. Increase ticket validity (sa-token.sso.ticket-timeout) if users idle on the callback page
  3. Ensure all sso-server nodes share the same Redis/storage so tickets created on one node are visible on another

Example fix

// before
TicketModel tm = ssoServerTemplate.checkTicketParamAndDelete(ticket);

// after — degrade to re-login instead of a 500
try {
    TicketModel tm = ssoServerTemplate.checkTicketParamAndDelete(ticket);
} catch (SaSsoException e) {
    if(SaSsoErrorCode.CODE_30004 == e.getCode()) {
        return SaHolder.getResponse().redirect(ssoServerTemplate.buildServerAuthUrl());
    }
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if(ssoServerTemplate.getTicket(ticket) == null) {
    // ticket unknown: redirect to auth flow instead of validating
}

Try / catch

try { ssoServerTemplate.checkTicket(ticket); } catch (SaSsoException e) { if(SaSsoErrorCode.CODE_30004 == e.getCode()) { /* redirect to /sso/auth to reissue */ } else throw e; }

Prevention

When it happens

Trigger: Client calls the server's check-ticket endpoint with a ticket that was already deleted (tickets are removed on first validation via checkTicketParamAndDelete), has passed its timeout, or is a malformed/guessed value.

Common situations: User double-refreshes the client callback URL (first refresh consumed the ticket); the login page sat open longer than the ticket timeout; Redis data was flushed or the server restarted with in-memory storage; clock/store mismatch in a clustered server where the ticket was created on a different node without shared storage.

Related errors


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