今天,继续昨天的系列 Golang JWT 库升级,RegisteredClaims 取代 StandardClaims。 不得不说,golang-jwt/jwt 官方文档太晦涩了,很多细节都需要自己去探索。
RegisteredClaims 过期时间的设置
之前的 StandardClaims 的 exp 字段是 int64 类型,表示 Unix 时间戳。例如:
claims["exp"] = time.Now().Add(time.Second * sec).Unix() // 废弃 ⚠️
但是,最新的 RegisteredClaims 的 exp 字段(ExpiresAt)是 *jwt.NumericDate 类型:
type NumericDate struct {
time.Time
}
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"`
}
设置 ExpiresAt 字段示例:
now := time.Now()
claims := &jwt.RegisteredClaims{
Issuer: "Test",
ExpiresAt: &jwt.NumericDate{Time: now.Add(time.Hour * 24)},
}
为何这里使用 *NumericDate
这是一个非常有趣的问题,为何要使用 *NumericDate 代表时间,而不是直接使用 *time.Time 或者 time.Time,或者是 int64 呢?
如果直接使用 time.Time,会导致序列化之后,变成了字符串形式,形如:"2024-12-31T00:00:00Z"。 但是 JWT Token 的 RFC 7519 规范中,要求 exp 字段必须是一个数字,表示 Unix 时间戳。单位为秒。 封装一层,可以方便的自定义序列化和反序列化逻辑。
使用指针 (*NumericDate) 的原因:区分“零值”和“未设置”。 这是一个非常经典的Go语言设计模式,用于处理可选字段。 如果一个 time.Time 字段是零值,我们无法判断是用户故意设置了一个远古的日期,还是这个字段根本就没设置。 对于 exp 来说,0 是一个有意义的值(1970年1月1日),但它也经常是未初始化变量的值。如果将其编码到JWT中,Token会在1970年就“过期”,这显然是错误的。
使用指针 (*NumericDate) 可以完美解决这个问题:
- 如果指针为 nil: 表示这个声明未被设置。在序列化(编码)成JWT时,这个字段会被完全忽略(因为标签有 omitempty)。
- 如果指针指向一个有效的 NumericDate: 表示这个声明已被显式设置。在序列化时,会将其编码为对应的数字时间戳。
至于为何不使用 int64,估计是为了减少类型转换的麻烦。
不过,目前的实现方案确实有点绕,费脑子。
MapClaims: 完全自定义的 Claims
如果不想使用 RegisteredClaims 的内置字段,可以使用 MapClaims 来完全自定义 Claims 字段。
var (
key *ecdsa.PrivateKey
t *jwt.Token
s string
)
key = /* Load key from somewhere, for example a file */
t = jwt.NewWithClaims(jwt.SigningMethodES256,
jwt.MapClaims{
"iss": "my-auth-server",
"sub": "john",
"foo": 2,
})
s = t.SignedString(key)
但是,我的使用场景来看,RegisteredClaims 的内置字段已经足够满足需求了。
扩展字段
或者想基于 RegisteredClaims 添加自定义字段,可以这样做:
type MyCustomClaims struct {
UID int `json:"uid"`
Role string `json:"role"`
jwt.RegisteredClaims
}
参考资料
- Github 仓库:https://github.com/golang-jwt/jwt
- 如何创建 JWT Token: https://golang-jwt.github.io/jwt/usage/create/
- 解析及验证:https://pkg.go.dev/github.com/golang-jwt/jwt/v5#example-Parse-Hmac
关于作者 🌱
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式