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
- Log in first (StpUtil.login) and pass the exact token value via StpUtil.getTokenValue() when opening the SSE connection.
- If the token expired, re-authenticate and reconnect with the fresh token; consider sa-token auto-renew (active-timeout / renewal config) for long connections.
- Strip any 'Bearer ' prefix and whitespace from the satoken parameter before calling createSse.
- 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
- Open SSE connections only after a successful login, using StpUtil.getTokenValue() for the current token.
- On the browser side, treat NotLoginException responses as a re-login signal, not a retry trigger.
- Strip 'Bearer ' prefixes before passing the token as a query parameter.
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.