dgrijalva/jwt-go 可以将声明投射到 MapClaims 而不是 StandardClaims?

dgrijalva/jwt-go can cast claims to MapClaims but not StandardClaims?

我正在使用以下代码创建令牌

token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
    Subject: string(user.Id),
})

tokenString, err := token.SignedString([]byte("secret"))

并尝试使用以下代码解析它们

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, UnauthorizedError
    }

    return []byte("secret"), nil
})
if err != nil {
    return -1, UnauthorizedError
}

if !token.Valid {
    return -1, UnauthorizedError
}

claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
    return -1, UnauthorizedError
}

logrus.Info(claims)

为什么我不能将我的声明投射到 StandardClaims 并访问 claims.Subject?

Why can I not cast my claims to StandardClaims and access claims.Subject?

从概念上讲,这是不可能的,因为 jwt.Parse 函数 by default 将声明解析为 jwt.MapClaims 的实例。这是与 jwt.StandardClaims 根本不同的数据结构;编译器无法使用简单的类型转换自动在两者之间进行转换,因为它们以不同的方式表示数据。

分辨率

该库提供了 ParseWithClaims 函数,它允许您指定自己的 jwt.Claims 接口的实现程序,以便将声明解码到其中。您可以传递 jwt.StandardClaims 的实例。例如:

token, err := jwt.ParseWithClaims(
    tokenString, &jwt.StandardClaims{},
    func(token *jwt.Token) (interface{}, error) {
        // ...
    },
)

如果可能,声明将被解析并解码为变量 token.Claims。存储到此变量中的值的基础 (dynamic1) 类型将为 *jwt.StandardClaims。这可以在类型断言中用于从接口类型中恢复标准声明:

claims, ok := token.Claims.(*jwt.StandardClaims)
if !ok {
    // handle type assertion failure
}
// do something with "claims"

让我们进一步深入研究语言规范和库定义,以便对这一说法进行更严格的评估。

对包类型的背景了解

jwt.MapClaims 是定义的类型,基础类型为 map[string]interface{} (code).

jwt.StandardClaims 是定义的 struct 类型 (code):

type StandardClaims struct {
    // Field set elided for brevity, as it is unimportant to the
    // answer.
}

两种类型都实现了 jwt.Claims 接口类型 (definition),因此可以分配给 jwt.Claims:

类型的变量
type Claims interface {
    Valid() bool
}

Token 结构有一个 field 称为 Claims 类型 jwt.Claims – 任何实现 Claims 接口的值都可以分配给 Claims.

类型断言定义

T 不是接口类型时,x.(T) 形式的类型断言表达式的语言规范 specifies 有效, 动态类型[ x 的 =105=]1 必须与类型 T 相同。在这里,您希望评估断言 x.(*jwt.StandardClaims);即断言类型不是接口类型。

jwt.Parsecode 最终在默认解析器上调用 jwt.ParseWithClaims,为声明目标传递 jwt.MapClaims 的实例:

func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
    return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}

因此您生成的令牌中 Claims 字段的动态类型是 jwt.MapClaims 类型。此类型与 jwt.StandardClaims 类型 不同 (即不相同),因为用户定义类型 always different 与除自身之外的任何其他类型不同。因此,类型断言失败。


1动态类型 (ref):回想一下在 Go 中接口类型是隐式实现的通过实现接口中指定方法的超集的任何类型。如果我们定义一个MyInterface类型的接口,变量声明var x MyInterface有静态类型(在编译时定义)MyInterface。但是,在运行时,我们可以将实现 MyInterface 的任何值分配给 x。任意时刻赋值给x的值的底层类型(实现接口的类型)指定变量的动态类型