dromara/Sa-Token · error · NotLoginException

无效 token

Error message

无效 token

What it means

NotLoginException (type from StpUtil.TYPE, NotLoginException.INVALID_TOKEN) thrown by the SSE demo's SseEmitterHolder.createSse when StpUtil.getLoginIdByToken(satoken) returns null — i.e. the supplied token does not correspond to any active login session. Using the framework's NotLoginException (instead of a generic RuntimeException) lets sa-token's global exception handling render the standard not-logged-in response.

Source

Thrown at sa-token-demo/sa-token-demo-sse/src/main/java/com/pj/util/SseEmitterHolder.java:30

/**
 * SSE 连接管理器
 *
 * @author click33
 * @since 2025/4/11
 */
public class SseEmitterHolder {

    public static final Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    /**
     * 创建客户端
     */
    public static SseEmitter createSse(String satoken) {

        Object loginId = StpUtil.getLoginIdByToken(satoken);
        if(loginId == null) {
            throw new NotLoginException("无效 token", StpUtil.TYPE, NotLoginException.INVALID_TOKEN);
        }
        long uid = SaFoxUtil.getValueByType(loginId, Long.class);

        // 默认 30 秒超时,设置为 0L 则永不超时
        SseEmitter sseEmitter = new SseEmitter(600 * 1000L);
        sseEmitterMap.put(satoken, sseEmitter);
        System.out.println("连接成功:satoken=" + satoken + ",uid=" + uid);

        // 完成后回调
        sseEmitter.onCompletion(() -> {
            System.out.println("结束连接:satoken=" + satoken + ",uid=" + uid);
            sseEmitterMap.remove(satoken);
        });

        //超时回调
        sseEmitter.onTimeout(() -> {
            System.out.println("连接超时:satoken=" + satoken + ",uid=" + uid);
        });

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Log in first (StpUtil.login) and pass the exact token value via StpUtil.getTokenValue() when opening the SSE connection.
  2. If the token expired, re-authenticate and reconnect with the fresh token; consider sa-token auto-renew (active-timeout / renewal config) for long connections.
  3. Strip any 'Bearer ' prefix and whitespace from the satoken parameter before calling createSse.
  4. On the client, treat NotLoginException as a signal to redirect to login rather than retry-looping the SSE connect.

Example fix

// before
String satoken = request.getParameter("satoken"); // may contain "Bearer xxx"
SseEmitter emitter = SseEmitterHolder.createSse(satoken);

// after
String satoken = request.getParameter("satoken");
if (satoken != null && satoken.startsWith("Bearer ")) {
    satoken = satoken.substring(7);
}
if (StpUtil.getLoginIdByToken(satoken) == null) {
    return ResponseEntity.status(401).body("please login first");
}
SseEmitter emitter = SseEmitterHolder.createSse(satoken);
Defensive patterns

Strategy: validation

Validate before calling

String token = rawToken == null ? null : rawToken.replaceFirst("^Bearer ", "").trim();
if (token == null || StpUtil.getLoginIdByToken(token) == null) {
    return 401; // do not call SseEmitterHolder.createSse
}
SseEmitter emitter = SseEmitterHolder.createSse(token);

Type guard

boolean isValidToken(String t) { return t != null && StpUtil.getLoginIdByToken(t) != null; }

Try / catch

try { SseEmitterHolder.createSse(token); } catch (NotLoginException e) { // reply 401, client re-login then reconnect }

Prevention

When it happens

Trigger: Opening the SSE endpoint (/sse/connect?satoken=xxx) with a token that was never issued, has been logged out (StpUtil.logout invalidates it), kicked out, or expired per sa-token timeout config. Also when the token is passed with stray whitespace or the 'Bearer ' prefix still attached.

Common situations: Long-lived SSE connections outliving the token timeout (token expires while the client intends to reconnect); front-end storing a stale token after re-login; gateway stripping query parameters so satoken arrives empty.

Related errors


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