apache/shenyu · error · AuthenticationException

token is error.

Error message

token is error.

What it means

Final step of ShiroRealm authentication: JwtUtils.verifyToken(token, jwtProperties.getSecretKey()) returned false, so the JWT signature/timestamp does not validate against the configured secret key and 'token is error.' is thrown.

Solutions

  1. Re-login to obtain a fresh token
  2. Verify shenyu.jwt.secret-key (jwtProperties) is identical across all admin instances and unchanged since the token was issued
  3. Decode the JWT and check the exp claim; discard expired tokens client-side
  4. Ensure the token is transmitted intact (no truncation, correct Authorization header format)

Example fix

// before (application.yml)
shenyu:
  jwt:
    secret-key: dev-only-key
// after — align key across environments/replicas
shenyu:
  jwt:
    secret-key: ${JWT_SECRET_KEY}
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side expiry check before calling
boolean notExpired = parseClaims(token).exp * 1000L > System.currentTimeMillis();

Type guard

boolean isWellFormedJwt(String t) { return t != null && t.split("\\.").length == 3; }

Try / catch

try { call(token); } catch (AuthenticationException e) { if ("token is error.".equals(e.getMessage())) { token = relogin(); retryOnce(); } }

Prevention

When it happens

Trigger: Token expired, signature tampered, or token signed with a different secret than shenyu.jwt.secret-key configured on the admin; also tokens carried over between deployments where the JWT key changed.

Common situations: JWT secret changed or different across admin replicas after config update; clock skew making an unexpired-looking token verify as expired; copy-pasted token truncated; default secret mismatch between environments.

Related errors


AI-assisted analysis of apache/shenyu@567142e072 (2026-09-12). Data as JSON: /api/errors/8e991362d5296113. Report an issue: GitHub.

Appendix: source

Thrown at shenyu-admin/src/main/java/org/apache/shenyu/admin/shiro/config/ShiroRealm.java:120

            throw new AuthenticationException("userName is null");
        }

        DashboardUserVO dashboardUserVO = dashboardUserService.findByUserName(userName);
        if (Objects.isNull(dashboardUserVO)) {
            throw new AuthenticationException(String.format("userName(%s) can not be found.", userName));
        }
        if (!Boolean.TRUE.equals(dashboardUserVO.getEnabled())) {
            throw new AuthenticationException(String.format("user(%s) is disabled.", userName));
        }
        String clientIdFromToken = JwtUtils.getClientId(token);
        if (StringUtils.isNotEmpty(clientIdFromToken)
                && StringUtils.isNotEmpty(dashboardUserVO.getClientId())
                && !StringUtils.equals(dashboardUserVO.getClientId(), clientIdFromToken)) {
            throw new AuthenticationException("clientId is invalid or does not match");
        }

        if (!JwtUtils.verifyToken(token, jwtProperties.getSecretKey())) {
            throw new AuthenticationException("token is error.");
        }

        return new SimpleAuthenticationInfo(UserInfo.builder()
                .userName(userName)
                .userId(dashboardUserVO.getId())
                .build(), token, this.getName());
    }
}

View on GitHub (pinned to 567142e072)