dromara/Sa-Token · error · SaJwtException

30201

30201

Error message

jwt 解析失败:

What it means

Thrown by SaJwtTemplate.parseToken when JWT.of(token) raises JWTException or JSONException — the string is not structurally a valid JWT (header.payload.signature, base64 JSON parts). Code 30201 marks a jwt parse failure; the original exception is chained.

Source

Thrown at sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java:186

     */
    public JWT parseToken(String token, String loginType, String keyt, boolean isCheckTimeout) {

    	// 秘钥不可以为空
    	if(SaFoxUtil.isEmpty(keyt)) {
    		throw new SaJwtException("请配置 jwt 秘钥");
    	}

    	// 如果token为null 
    	if(token == null) {
    		throw new SaJwtException("jwt 字符串不可为空");
    	}
    	
    	// 解析 
    	JWT jwt;
    	try {
    		jwt = JWT.of(token);
		} catch (JWTException | JSONException e) {
    		throw new SaJwtException("jwt 解析失败:" + token, e).setCode(SaJwtErrorCode.CODE_30201);
		}
    	JSONObject payloads = jwt.getPayloads();
    	
    	// 校验 Token 签名
		boolean verify = jwt.setSigner(createSigner(keyt)).verify();
    	if( ! verify) {
    		throw new SaJwtException("jwt 签名无效:" + token).setCode(SaJwtErrorCode.CODE_30202);
    	}

    	// 校验 loginType 
    	if( ! Objects.equals(loginType, payloads.getStr(LOGIN_TYPE))) {
    		throw new SaJwtException("jwt loginType 无效:" + token).setCode(SaJwtErrorCode.CODE_30203);
    	}
    	
    	// 校验 Token 有效期
    	if(isCheckTimeout) {
    		Long effTime = payloads.getLong(EFF, 0L);
        	if(effTime != NEVER_EXPIRE) {

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Log/inspect the exact token string received (length, dots, base64 charset) and compare with the issued token
  2. Strip the 'Bearer ' prefix before parsing if you extract the header yourself
  3. Make the client send the token exactly as issued, without re-encoding or truncation

Example fix

// before
String raw = request.getHeader("Authorization"); // "Bearer eyJ..."
parseToken(raw, loginType, secret, true); // 'Bearer ...' is not a JWT -> 30201

// after
String raw = request.getHeader("Authorization");
String token = StrUtil.removePrefix(raw, "Bearer ").trim();
parseToken(token, loginType, secret, true);
Defensive patterns

Strategy: validation

Validate before calling

String t = StrUtil.removePrefix(raw, "Bearer ").trim();
boolean looksLikeJwt = t.split("\\.").length == 3;
if (!looksLikeJwt) return unauthorized("malformed token");

Try / catch

catch (SaJwtException e) { if (e.getCode() == SaJwtErrorCode.CODE_30201) { /* 401 malformed; log token length not full value */ } }

Prevention

When it happens

Trigger: Passing a malformed token: not three dot-separated segments, invalid base64, or payload that is not JSON. Happens before signature verification, so even a correctly-signed blob with a corrupted character fails here.

Common situations: Client truncates or wraps the token (log copy-paste with line breaks); token URL-encoded twice so '.' handling breaks; opaque session token sent to a JWT-mode endpoint; token prefixed with 'Bearer ' but not stripped; hand-crafted test tokens.

Related errors


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