{"record":{"id":"782ef7d2d8a387d0","repo":"flipped-aurora/gin-vue-admin","slug":"token","errorCode":null,"errorMessage":"token已过期","messagePattern":"token已过期","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/jwt.go","lineNumber":19,"sourceCode":"package utils\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"time\"\n\n\t\"github.com/flipped-aurora/gin-vue-admin/server/global\"\n\t\"github.com/flipped-aurora/gin-vue-admin/server/model/system/request\"\n\tjwt \"github.com/golang-jwt/jwt/v5\"\n)\n\ntype JWT struct {\n\tSigningKey []byte\n}\n\nvar (\n\tTokenValid            = errors.New(\"未知错误\")\n\tTokenExpired          = errors.New(\"token已过期\")\n\tTokenNotValidYet      = errors.New(\"token尚未激活\")\n\tTokenMalformed        = errors.New(\"这不是一个token\")\n\tTokenSignatureInvalid = errors.New(\"无效签名\")\n\tTokenInvalid          = errors.New(\"无法处理此token\")\n)\n\nfunc NewJWT() *JWT {\n\treturn &JWT{\n\t\t[]byte(global.GVA_CONFIG.JWT.SigningKey),\n\t}\n}\n\nfunc (j *JWT) CreateClaims(baseClaims request.BaseClaims) request.CustomClaims {\n\tbf, _ := ParseDuration(global.GVA_CONFIG.JWT.BufferTime)\n\tep, _ := ParseDuration(global.GVA_CONFIG.JWT.ExpiresTime)\n\tclaims := request.CustomClaims{\n\t\tBaseClaims: baseClaims,\n\t\tBufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/utils/jwt.go#L1-L37","documentation":"TokenExpired is returned by ParseToken when the JWT's exp claim is in the past. The token was correctly signed and formed, but its lifetime has elapsed, so it must not be accepted.","triggerScenarios":"Any request whose x-token/Authorization token has an exp earlier than the current time — long-lived sessions, tokens issued long ago, or clocks skewed far ahead on the server.","commonSituations":"User leaves a tab open past ExpiresAt; client caches a token across deployments; server clock drift (NTP failure) making valid tokens appear expired; very short ExpiresAt configured in config.yaml.","solutions":["Have the client obtain a fresh token via the login (or refresh) endpoint and retry.","If refresh tokens are enabled, exchange the expired access token using the refresh flow instead of forcing re-login.","Increase JWT.ExpiresAt in config.yaml if the configured lifetime is unintentionally short.","Sync the server clock (NTP) if skew is causing premature expiry."],"exampleFix":"// client before\nfetch('/api/user/info', { headers: { 'x-token': staleToken } }) // 401 token已过期\n// after\nif (res.code === 401 && isExpired(res.msg)) {\n    await reLogin() // or use refreshToken endpoint\n    retry(request)\n}","handlingStrategy":"retry","validationCode":"const EXP_GRACE = 60 * 1000\nfunction willExpireSoon(token) {\n    const payload = JSON.parse(atob(token.split('.')[1]))\n    return payload.exp * 1000 - Date.now() < EXP_GRACE\n}\nif (willExpireSoon(token)) await refreshToken()","typeGuard":"function parseExp(token) {\n    try { return JSON.parse(atob(token.split('.')[1])).exp * 1000 } catch { return 0 }\n}","tryCatchPattern":"claims, err := utils.ParseToken(token)\nif errors.Is(err, utils.TokenExpired) {\n    // client flow: redirect to login or call refresh-token endpoint, then retry once\n    return nil, ErrNeedReAuth\n}","preventionTips":["Implement proactive token refresh before exp rather than after a 401","Set a reasonable JWT.ExpiresAt in config.yaml and keep server clocks NTP-synced","Handle the expired sentinel explicitly in middleware to return 401 with a recognizable msg so clients re-auth"],"tags":["jwt","auth","go","token-expiry"],"backgroundTag":"jwt-token-expired","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}