{"record":{"id":"16fd99f84d5ff874","repo":"dromara/Sa-Token","slug":"30201","errorCode":"30201","errorMessage":"jwt 解析失败：","messagePattern":"jwt 解析失败：","errorType":"exception","errorClass":"SaJwtException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java","lineNumber":186,"sourceCode":"     */\n    public JWT parseToken(String token, String loginType, String keyt, boolean isCheckTimeout) {\n\n    \t// 秘钥不可以为空\n    \tif(SaFoxUtil.isEmpty(keyt)) {\n    \t\tthrow new SaJwtException(\"请配置 jwt 秘钥\");\n    \t}\n\n    \t// 如果token为null \n    \tif(token == null) {\n    \t\tthrow new SaJwtException(\"jwt 字符串不可为空\");\n    \t}\n    \t\n    \t// 解析 \n    \tJWT jwt;\n    \ttry {\n    \t\tjwt = JWT.of(token);\n\t\t} catch (JWTException | JSONException e) {\n    \t\tthrow new SaJwtException(\"jwt 解析失败：\" + token, e).setCode(SaJwtErrorCode.CODE_30201);\n\t\t}\n    \tJSONObject payloads = jwt.getPayloads();\n    \t\n    \t// 校验 Token 签名\n\t\tboolean verify = jwt.setSigner(createSigner(keyt)).verify();\n    \tif( ! verify) {\n    \t\tthrow new SaJwtException(\"jwt 签名无效：\" + token).setCode(SaJwtErrorCode.CODE_30202);\n    \t}\n\n    \t// 校验 loginType \n    \tif( ! Objects.equals(loginType, payloads.getStr(LOGIN_TYPE))) {\n    \t\tthrow new SaJwtException(\"jwt loginType 无效：\" + token).setCode(SaJwtErrorCode.CODE_30203);\n    \t}\n    \t\n    \t// 校验 Token 有效期\n    \tif(isCheckTimeout) {\n    \t\tLong effTime = payloads.getLong(EFF, 0L);\n        \tif(effTime != NEVER_EXPIRE) {","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java#L168-L204","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Log/inspect the exact token string received (length, dots, base64 charset) and compare with the issued token","Strip the 'Bearer ' prefix before parsing if you extract the header yourself","Make the client send the token exactly as issued, without re-encoding or truncation"],"exampleFix":"// before\nString raw = request.getHeader(\"Authorization\"); // \"Bearer eyJ...\"\nparseToken(raw, loginType, secret, true); // 'Bearer ...' is not a JWT -> 30201\n\n// after\nString raw = request.getHeader(\"Authorization\");\nString token = StrUtil.removePrefix(raw, \"Bearer \").trim();\nparseToken(token, loginType, secret, true);","handlingStrategy":"validation","validationCode":"String t = StrUtil.removePrefix(raw, \"Bearer \").trim();\nboolean looksLikeJwt = t.split(\"\\\\.\").length == 3;\nif (!looksLikeJwt) return unauthorized(\"malformed token\");","typeGuard":null,"tryCatchPattern":"catch (SaJwtException e) { if (e.getCode() == SaJwtErrorCode.CODE_30201) { /* 401 malformed; log token length not full value */ } }","preventionTips":["Strip 'Bearer ' prefix at the extraction boundary exactly once","Never re-encode or rewrite tokens client-side; treat them as opaque strings"],"tags":["sa-token","jwt","parse","malformed-token"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}