Golang jwt 工具

JWT 介绍

JWT 全程时 JSON Web Token,是目前最流行的跨域身份验证解决方案。JWT 的验证流程 :


1 用户使用账号、密码登录应用,登录的请求发送到 Authentication Server。
2 Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。
3 客户端请求接口时,在请求头带上 JWT。
4 Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。

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 语言实现 JWT

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
}