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
- Redirect the user back to the SSO login/auth flow instead of showing a raw error — the ticket is gone and must be reissued
- Increase ticket validity (sa-token.sso.ticket-timeout) if users idle on the callback page
- 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
- Make ticket validation idempotent-safe: tolerate double callbacks by redirecting to re-auth
- Use shared Redis storage across server nodes and size ticket-timeout realistically
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.