Golang JWT 库升级,RegisteredClaims 取代 StandardClaims

更新日期: 2025-08-26 阅读次数: 79 字数: 1928 分类: golang

最近在升级 golang jwt 依赖库时,发现 dgrijalva/jwt-go 已经废弃,现在变成了 golang-jwt/jwt。 升级的后,代码各种报错,原本内置的 StandardClaims 不见了,取而代之的是 RegisteredClaims。

今天下午从酒店干苦力回来,又累又困,扛不住眯了一会,也没有兴致写业务逻辑了。干脆把新增的 RegisteredClaims 结构体了解了一下。

JWT Token 是我刚开始接触 golang 时,就用的一个库。现有项目的 JWT Token 逻辑也是大概 5 年前写的, 一直没有更新过。一个最初写的小程序后台服务,就包含这个模块,稳定运行了 5 年,积累了 50 万用户, 我都没有更新过代码。。。 稳定得令人发指。

jwt-go github 地址变更

https://github.com/dgrijalva/jwt-go

仓库地址已变更为:

https://github.com/golang-jwt/jwt

原因是作者没空维护了。

原来的引入方式

import "github.com/dgrijalva/jwt-go"

需要变更为:

import "github.com/golang-jwt/jwt/v5"

undefined: jwt.StandardClaims

移除 StandardClaims 的修改说明:

https://github.com/golang-jwt/jwt/pull/235

This PR removes the old legacy standard claims, which have been deprecated since the beginning of the v4 module in favor of the newer RegisteredClaims. Removing them before any further changes to the validation API is quite useful, as less code needs to be adapated.

claim

中文翻译是:要求, 声称

RegisteredClaims 结构体

type RegisteredClaims struct {
	Issuer string `json:"iss,omitempty"`
	Subject string `json:"sub,omitempty"`
	Audience ClaimStrings `json:"aud,omitempty"`
	ExpiresAt *NumericDate `json:"exp,omitempty"`
	NotBefore *NumericDate `json:"nbf,omitempty"`
	IssuedAt *NumericDate `json:"iat,omitempty"`
	ID string `json:"jti,omitempty"`
}

简单来说

  • 身份 (iss, sub, aud):回答了“谁发的?”、“给谁的?”、“代表谁?”这三个关键问题。
  • 有效期 (exp, nbf, iat):定义了令牌的生命周期,确保它只在必要的时间窗口内有效。
  • 唯一性 (jti):提供了防止令牌被重复使用的机制。

"iss" (Issuer) Claim

The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

签发者。标识签发该JWT的主体(通常是服务器或认证服务的名称/URL)。

使用场景:

  • 多租户系统:在一个系统为多个不同发行方(例如,多个客户端或合作伙伴)服务时,可以用 iss 来区分令牌是哪个发行方签发的,从而决定其可访问的资源。
  • 验证令牌来源:接收方(资源服务器)会检查 iss 字段是否是自己信任的签发者列表中的一个,以防止处理来自不可信来源的恶意令牌。
  • 示例:"iss": "https://your-auth-server.com"

"sub" (Subject) Claim

The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL.

主题。标识该JWT的主题,即该令牌所代表的用户(或客户端等主体)的身份标识。

使用场景:

  • 用户身份标识:这是最核心的字段,通常包含用户的唯一ID(如数据库中的主键user_id)。资源服务器会根据这个ID来确定是哪个用户在请求数据。
  • 访问控制:在API中,可以根据 sub 来查询用户的权限和角色,从而决定是否允许其执行某个操作。
  • 示例:"sub": "1234567890" 或 "sub": "user@example.com"

原来我之前使用的写法是:

type MyCustomClaims struct {
	UID  int    `json:"uid"`
	Role string `json:"role"`
	jwt.StandardClaims
}

还扩展了一个 UID 字段,其实不是标准的做法,还是应该把用户名或者用户 ID 存储到 subject 字段中。 唯一纠结的地方是,subject 是字符串类型,而我习惯用 int 类型的用户 ID。 不过这个 string 类型的设计是合理的,毕竟像分布式用户 ID 都是字符串类型的,如果设计成 int 类型就完全没法搞了。现在只是可以做一下类型转换。

"aud" (Audience) Claim

The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.

受众(听众、观众、读者群体)。标识该JWT的预期接收者( audience )。它可以是一个字符串数组(ClaimStrings),意味着一个令牌可以同时用于多个服务。(我经常把 Audience 跟 Audit 这两个单词搞混,Audit 是审计、核查的意思)

使用场景:

  • 服务间区分:在一个微服务架构中,一个认证令牌可能需要在网关、用户服务和订单服务等多个服务间传递。aud 可以指定这个令牌是专门给哪个或哪些服务使用的。例如,一个令牌的 aud 是 ["user-service", "order-service"],而网关可能会拒绝一个 aud 仅为 "log-service" 的令牌访问用户API。
  • 防止令牌滥用:确保一个发给服务A的令牌不能被恶意用于访问服务B。
  • 示例:"aud": "https://api.example.com" 或 "aud": ["service-a", "service-b"]

这个非常有用,例如我正在开发的人事管理系统的协作功能(人事系统办理入职功能的一起编辑填写功能的实现方案),有多个模块会用到临时 token。如,正式员工入职时的协作编辑,以及面试人员(人才录入)的信息填写。

  1. 业务场景 tag,例如是入职还是人才录入。这里就可以用 Audience 来存储。
  2. 业务的值 value,例如是 staff id,还是 candicate id。就可以用 Subject 存储,就不需要再新建一个 UID 的字段了。

"exp" (Expiration Time) Claim

The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

过期时间。这个是最常使用的字段。

ExpiresAt *NumericDate `json:"exp,omitempty"`

NumericDate 的定义:

type NumericDate struct {
	time.Time
}

"nbf" (Not Before) Claim

The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

"iat" (Issued At) Claim

The "iat" (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL.

签发时间。记录令牌被签发的绝对时间。

使用场景:

  • 计算令牌年龄:可以用来计算令牌已经存活了多久。例如,一个资源服务器可以有一个策略:即使 exp 还没到,但如果令牌签发时间 (iat) 过于久远(比如超过24小时),出于安全考虑也会拒绝它。
  • 审计与日志:用于追踪和日志记录,帮助诊断问题。

"jti" (JWT ID) Claim

The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case-sensitive string. Use of this claim is OPTIONAL.

JWT ID。为 JWT 提供一个唯一标识符。

使用场景:

  • 防止重放攻击(Replay Attack):这是其主要用途。服务器可以将已使用过的 jti 标识符在一个短期缓存(例如,缓存到该令牌的 exp 时间)中标记为“已使用”。如果收到一个带有相同 jti 的令牌,即使其他信息都有效,服务器也会拒绝它,因为它可能是一个被拦截并重复发送的恶意请求。
  • 唯一性追踪:为每个颁发的令牌提供一个唯一ID,便于在分布式系统中进行追踪。

这个 JWT ID 可以填写为一个随机生成的 UUID,确保其唯一性。 而且 JWT ID 也适合存储到数据库中,远比 JWT 整个长串要短很多。

继续阅读

Golang JWT Token 升级之二,RegisteredClaims 的使用细节

关于作者 🌱

我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式