JWT 全程时 JSON Web Token,是目前最流行的跨域身份验证解决方案。JWT 的验证流程 :
1 用户使用账号、密码登录应用,登录的请求发送到 Authentication Server。
2 Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。
3 客户端请求接口时,在请求头带上 JWT。
4 Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。
JWT 不需要依赖服务端数据存储,令牌信息存储在客户端。所以关键在于生成 JWT 和解析 JWT 这两个地方。
JWT 数据结构如下图 :
JWT为一串字符串,通过"."分隔符分为三个子串。每一个子串表示了一个功能块,总共有以下三个部分:
1 JWT头
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
alg 属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);
typ 属性表示令牌的类型,JWT令牌统一写为JWT。
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
2 有效载荷
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
{
"sub": "1234567890",
"name": "test...",
}
请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
JSON对象也使用Base64 URL算法转换为字符串保存。
3 签名哈希
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
4 Base64URL算法
如前所述,JWT头和有效载荷序列化的算法都用到了Base64URL。该算法和常见Base64算法类似,稍有差别。
作为令牌的JWT可以放在URL中(例如api.example/?token=xxx)。 Base64中用的三个字符是"+","/"和"=",由于在URL中有特殊含义,因此Base64URL中对他们做了替换:"="去掉,"+"用"-"替换,"/"用"_"替换,这就是Base64URL算法。
GO 语言可以使用 : github.com/golang-jwt/jwt/v4 工具包来实现 JWT :
1. 生成 TOKEN
rand.Seed(time.Now().UnixNano())
randNum := rand.Int()
claims := jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Second * time.Duration(global.APITokenExpireTime)).Unix(),
Id: strconv.Itoa(randNum),
Issuer: "Issuer",
Subject: "Subject",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// global.APIKey :自行设置的密钥
signString, err := token.SignedString([]byte(global.APIKey))
if err != nil {
ctx.JSON(200, gin.H{"errcode": 400400, "data": "令牌创建失败"})
} else {
ctx.JSON(200, gin.H{"errcode": 0, "data": signString})
}
2. 验证 TOKEN
// 利用 Header 发送令牌
token := ctx.Request.Header.Get("RTK")
// 进行接口基础认证
_, err := jwt.ParseWithClaims(
token,
&jwt.StandardClaims{},
func(token *jwt.Token) (i interface{}, err error) {
// global.APIKey :自行设置的密钥
return []byte(global.APIKey), nil
},
)
if err != nil {
ctx.JSON(200, gin.H{
"errcode": 100003,
"error": "接口验证失败",
})
ctx.Abort()
return
}