jeecgboot/JeecgBoot · error · JeecgBoot401Exception

Token失效,请重新登录!

Error message

Token失效,请重新登录!

What it means

Thrown by TokenUtils.verifyToken when jwtTokenRefresh() returns false — the token's TTL has expired, or the password in the DB no longer matches the password hash baked into the token claim. The actual message comes from a Redis error-msg cache (PREFIX_USER_TOKEN_ERROR_MSG+token) or falls back to CommonConstant.TOKEN_IS_INVALID_MSG. Returns HTTP 401.

Source

Thrown at jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/common/util/TokenUtils.java:126

        if (username == null) {
            throw new JeecgBoot401Exception("token非法无效!");
        }

        // 查询用户信息
        LoginUser user = TokenUtils.getLoginUser(username, commonApi, redisUtil);
        //LoginUser user = commonApi.getUserByName(username);
        if (user == null) {
            throw new JeecgBoot401Exception("用户不存在!");
        }
        // 判断用户状态
        if (user.getStatus() != 1) {
            throw new JeecgBoot401Exception("账号已被锁定,请联系管理员!");
        }
        // 校验token是否超时失效 & 或者账号密码是否错误
        if (!jwtTokenRefresh(token, username, user.getPassword(), redisUtil)) {
            // 用户登录Token过期提示信息
            String userLoginTokenErrorMsg = oConvertUtils.getString(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN_ERROR_MSG + token));
            throw new JeecgBoot401Exception(oConvertUtils.isEmpty(userLoginTokenErrorMsg)? CommonConstant.TOKEN_IS_INVALID_MSG: userLoginTokenErrorMsg);
        }
        return true;
    }

    /**
     * 刷新token(保证用户在线操作不掉线)
     * @param token
     * @param userName
     * @param passWord
     * @param redisUtil
     * @return
     */
    private static boolean jwtTokenRefresh(String token, String userName, String passWord, RedisUtil redisUtil) {
        String cacheToken = oConvertUtils.getString(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
        if (oConvertUtils.isNotEmpty(cacheToken)) {
            // 校验token有效性
            if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                // 从token中解析客户端类型,保持续期时使用相同的客户端类型

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Re-authenticate: redirect to login and obtain a fresh token.
  2. If single-device login is enforced, the prior session token is intentionally killed — this is expected, not a bug.
  3. After a password change, all prior tokens are by design invalid; log in again with the new password.
  4. To extend sessions, increase jeecg.token.expire / the refresh window in application.yml rather than disabling the check.

Example fix

// before
// token expired after 30 min idle -> 401 'Token失效,请重新登录!'

// after (frontend interceptor)
if (msg.includes('Token失效') || status === 401) {
  store.dispatch('Logout').then(() => router.push('/user/login'));
}
Defensive patterns

Strategy: retry

Validate before calling

// client-side: refresh window before the call
decodeJwt(token).exp * 1000 - Date.now() < 60000 -> call /sys/refresh or re-login

Type guard

public static boolean tokenLikelyFresh(String token, long ttlMs){
    try { return Jwts.parser().parseClaimsJws(token).getBody().getExpiration().getTime() - System.currentTimeMillis() > ttlMs; }
    catch (Exception e) { return false; }
}

Try / catch

try { TokenUtils.verifyToken(token, ...); }
catch (JeecgBoot401Exception e) { if (expired(e)) silentRefresh().orElseGet(this::relogin); }

Prevention

When it happens

Trigger: User idle longer than the token TTL (jeecg.token.expire, default ~30 min) and the sliding-refresh window also expired; the user changed their password so the token's embedded password hash no longer matches; multiple logins caused the single-session token to be invalidated.

Common situations: Leaving the app open overnight; password change on another device; token sliding-refresh window (jwtTokenRefresh's allowed skew) exhausted; clock drift between client and server.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/9d643c07711418bf. Report an issue: GitHub.