JWT介绍
JWT (JSON Web Token) 是一种在网络上用于身份验证的简洁、自包含的方法。它是由三部分组成的一个JSON对象,分别是头部(Header)、载荷(Payload)以及签名(Signature)。头部包含了令牌的类型和加密算法信息,载荷则包含了所需要传递的数据(通常包括一些权限信息和用户标识),签名用于验证令牌的完整性和有效性。
JWT主要用于两个场景:
- 认证:一旦用户登录,每个后续请求将包括JWT,允许用户访问允许的路由、服务和资源。
- 信息交换:JWT是在各方之间安全传输信息的好方法,因为它们可以进行数字签名,例如使用公钥/私钥对。
JWT数据格式
JWT 是一种紧凑的、URL安全的方式来表示要在双方之间传递的信息。一个JWT实际上由三个部分组成,它们之间用点(.
)分隔,即Header.Payload.Signature
。接下来,我们将详细介绍这三个部分的数据格式。
1. Header(头部)
头部通常由两部分组成:令牌的类型—通常是JWT
,和所用的签名或加密算法,例如HMAC SHA256
或RSA
。头部用JSON表示,然后用Base64Url编码成字符串。例如:
{
"alg": "HS256",
"typ": "JWT"
}
编码后可能会得到类似于这样的字符串:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. Payload(负载)
负载部分是由一系列的声明构成,声明是关于实体(通常是用户)和其他数据的信息。负载可以包含多个预定义的声明(也被称为Registered Claims),以及自定义的声明(Private Claims)。
预定义的声明可能会包括:
-
iss
(Issuer):签发者 -
exp
(Expiration Time):过期时间 -
sub
(Subject):主题 -
aud
(Audience):受众 -
iat
(Issued At):签发时间 -
nbf
(Not Before):生效时间 -
jti
(JWT ID):JWT的唯一身份标识
一个例子负载可能看起来这样(以JSON格式表示):
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}
这些信息也将被Base64Url编码,可能会得到类似于这样的字符串:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0
3. Signature(签名)
签名部分是对以上两个编码后的字符串进行签名,以便验证消息在传递过程中没有被篡改。首先,您需要指定一个密钥(如果使用的是HMAC SHA256算法),然后使用Header中指定的算法对Header和Payload进行签名。
例如,如果您有以下的Header和Payload:
HeaderEncoded.HeaderPayload
使用密钥对它们进行签名的伪代码可能是:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey)
签名后得到的字符串可能是:
dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
完整的JWT
将这三部分合并,用点(.
)分隔,就构成了完整的JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dBjftJeZ4CVPmTaoyL4IiArYfL4kH0jOspm6XwbcJXY
JWT标准具有极大的灵活性,可以在Payload中存储您需要的任何信息(但为了减少JWT的大小和安全原因,不应该存储敏感信息),并且通过签名保证了这些信息的完整性。
安装Go JWT库
Go 社区 提供了一个包github.com/golang-jwt/jwt
用于处理JWT。安装这个库非常简单,只需在项目目录下运行下列命令:
go get -u github.com/golang-jwt/jwt/v5
安装完成后,你可以在import
中按如下方式包含它:
import "github.com/golang-jwt/jwt/v5"
创建简单的token
要使用Go语言和HS256算法创建一个简单的JWT token,你需要做以下步骤:
首先,生成一个新的Token对象,这里使用HS256算法:
token := jwt.New(jwt.SigningMethodHS256)
接下来,通过SignedString
方法为这个token生成一个字符串形式的token,需要传递一个你用于签名的秘钥。
var mySigningKey = []byte("your-256-bit-secret")
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("出现错误: %v", err)
}
fmt.Println(strToken)
这将会生成一个不包含任何Claims(声明)的简单token。
创建携带参数的token
JWT的一个主要功能是能够携带信息。这些信息通过claims在Token中编码。例如,我们创建自定义的claims:
// 自定义Claims结构体
type MyClaims struct {
jwt.RegisteredClaims
Username string `json:"username"`
Admin bool `json:"admin"`
}
// 创建带有自定义Claims的token
claims := MyClaims{
RegisteredClaims: jwt.RegisteredClaims{},
Username: "my_username",
Admin: true,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 用于签名的秘钥
var mySigningKey = []byte("your-256-bit-secret")
// 生成字符串形式的token
strToken, err := token.SignedString(mySigningKey)
if err != nil {
log.Fatalf("出现错误: %v", err)
}
fmt.Println(strToken)
在前面代码中,我们定义了MyClaims
结构体来包含注册的claims以及一些自定义的信息。然后,我们依旧使用SignedString
来生成token字符串。
解析和验证token
解析并验证JWT的有效性非常重要,你可以通过以下方式来进行:
// 我们将使用相同的MyClaims结构体
tokenString := "你的JWT字符串"
// 我们需要定义一个函数,jwt包会使用它来解析tokenString
keyFunc := func(t *jwt.Token) (interface{}, error) {
// 确认使用的是你预期的签名算法
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("意外的签名方法: %v", t.Header["alg"])
}
// 返回jwt token的秘钥, 格式 []byte, 跟前面签名使用的key保持一致
return mySigningKey, nil
}
// 解析token
claims := &MyClaims{}
parsedToken, err := jwt.ParseWithClaims(tokenString, claims, keyFunc)
if err != nil {
log.Fatalf("解析错误: %v", err)
}
if !parsedToken.Valid {
log.Fatalf("无效Token")
}
// 在这一阶段, parsedToken已经经过验证,我们可以读取claims
fmt.Printf("User:%s,Admin:%v\n", claims.Username, claims.Admin)
在上述代码中,我们提供了token字符串、MyClaims
实例和秘钥函数keyFunc
给jwt.ParseWithClaims
函数。这个函数会验证签名并解析token,如果token有效,会填充claims
变量。