jwt的生成
1用到的方法
-
func NewWithClaims(method SigningMethod, claims Claims) *Token
-
func (t *Token) SignedString(key interface{}) (string, error)
2解析NewWithClaims中用到的参数
-
method,是一个 签名算法标识符,它的作用是告诉 JWT 库:在生成 JWT 时,使用 method这个算法对令牌进行签名,例如HS256算法(可以通过jwt引用,如jwt.SigningMethodHS256)。
-
claims,它是一个结构体,主要包含两部分
type MyClaims struct { //用于存储一些必要的信息
Username string `json:"username"`
jwt.StandardClaims //声明中的预定义字段
}
一、自定义数据部分(Username 字段)
Username string `json:"username"`
这是你根据业务需求添加的字段,用于存储 业务相关的数据。例如:
- 用户身份标识:如用户名、用户 ID、角色(
admin/user)。 - 业务状态:如会员等级、权限范围、临时会话 ID。
- 其他上下文:如设备信息、IP 地址(需注意隐私问题)。
二、标准声明部分(jwt.StandardClaims)
jwt.StandardClaims // 嵌入标准声明
这是 JWT 规范(RFC 7519)预定义的字段,用于存储 与令牌本身相关的元数据。具体的内容可以自己看,它的核心功能有
- 过期控制:通过
ExpiresAt限制令牌有效期,防止长期盗用。 - 时间窗口控制:通过
NotBefore设置令牌提前生效的时间。 - 身份溯源:通过
Issuer和Subject明确令牌来源和归属。
3解析SignedString中用到的参数
key
这是用来生成完整的jwt,在验证签名时,会使用这个key来重新计算,看得到的签名和这个要验证的key时候相同。
4实践
func SetToken(username string) (string, int) {
expireTime := time.Now().Add(time.Hour * 10) //设置过期时间,Add方法用于在当前时间基础上增加指定的时间间隔
SetClaims := MyClaims{ //设置jwt中储存什么的数据
username,
jwt.StandardClaims{
ExpiresAt: expireTime.Unix(), //其作用是将时间转换为 Unix 时间戳(即从 1970 年 1 月 1 日 00:00:00 UTC 起经过的秒数)
Issuer: "ginblog",
},//这两个参数可以自己查查是什么作用
}
reqClaim := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims) //reqClaim 是一个 jwt.Token 对象,它包含了 JWT 的所有声明
token, err := reqClaim.SignedString(JwtKey) //生成完整的jwt
if err != nil {
return "", errmsg.ERROR
}
return token, errmsg.SUCCSE
}
//这里面的errmsg都是自己定义的一些错误码,类似于404
补充一下,这里最好是了解一下jwt的生成流程,贴一个教程
验证token
1方法
setToken, _ :func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error)
2参数解析
| 标题 | |
|---|---|
| tokenStirng | 就是我们上面生成的完整的token |
| claims | 就是我们前面的 MyClaims |
| keyFunc | 回调函数,前面的写法都是固定的,return的JwtKey是我们前面提到的,用来验证的JwtKey |
实战
func CheckToken(token string) (*MyClaims, int) {
setToken, _ := jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) { //用于提供验证签名所需密钥的回调函数。
return JwtKey, nil //这里的Jwt是我前面自己定义的,如果把JwtKey换成一个错误的key,就会报错
})//这里省略的err
if key, ok := setToken.Claims.(*MyClaims); ok && setToken.Valid { //断言,看解析后得到的这个Claim和我的MyClaims是不是一样的,这个Valid是验证的结果
return key, errmsg.SUCCSE
} else {
return nil, errmsg.ERROR
}
}
补充一下这个setToken.Valid
setToken.Valid 是 JWT 验证的核心结果,它表示整个 JWT 是否合法。其返回值如下:
返回值含义
| 返回值 | 含义 |
|---|---|
true | JWT 完全合法: 1. 签名验证通过(密钥和算法匹配)。 2. 标准声明(如 ExpiresAt、NotBefore)合法。 |
false | JWT 无效: 1. 签名不匹配。 2. 声明验证失败(如已过期、未生效)。 3. 其他验证错误。 |
这个是可以捕获具体错误的,这里就不展开了。
讲一下我所理解的jwt
1什么是jwt
JWT(JSON Web Token)是一种安全传输信息的标准格式,它把用户信息(如 ID、角色)打包成一个加密字符串,用于在客户端和服务器之间传递身份验证信息。
2 jwt的组成
| 组成 | 内容 |
|---|---|
| Header | 一个 JSON 对象,描述 JWT 的元数据 |
| Payload | 一个 JSON 对象,用来存放实际需要传递的数据。可以简单理解为前面的MyClaims |
| Signature | Signature 部分是对前两部分的签名,通俗讲,它就是将前面两个用特定的方法编码后拼接在一起,然后和JwtKey用特定算法生成的一个东西。 |
3jwt的生成(jwt就理解为一个token)
-
生成Header和Payload
在前面的代码中,
下面这段代码生成了一个jwt.Token 对象,第一个参数是设定Header中的
alg属性,表示签名的算法(algorithm)。(这里的签名就是指 生成 Signature 的过程)。 第二个参数就可以相当于Payload。如果想要设置Header的其他内容,就可以用类似 reqClaim.Header["typ"] = "JWT",这样的形式设置。
reqClaim := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims)
这就完成了第一步
-
生成Signature(即签名)
需要的东西有:前面的Header,Payload,算法(前面的alg),密钥(前面代码提到的JwtKey),Base64URL 编码(好像是必须用这个)。
开始生成:将Header和Payload用Base64URL编码生成两个字符串,然后用"."连接生成一个新的字符串,将新的字符串和JwtKey(密钥)扔进算法里,就得到Signature。
3生成jwt
将编码过的Header和Payload,和生成的Signature用“.”连接起来,就是最终的jwt。
小小总结
reqClaim := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims)
这段代码为jwt的生成设定的必要的数据
然后依靠
reqClaim.SignedString(JwtKey)
这个代码最终生成了完整的jwt。
再讲一下jwt的解析和验证流程(就是前面流程的倒序)
1. 接收 JWT
客户端通常通过以下方式传递 JWT:
- HTTP Header:
Authorization: Bearer <JWT> - URL 参数:
?token=<JWT> - Cookie:
token=<JWT>
2. 解析基本结构
服务端将 JWT 按 . 拆分为三部分:
[encodedHeader].[encodedPayload].[encodedSignature]
3. 解码 Header
-
Base64URL 解码:将第一部分解码为 JSON 字符串。
-
解析 JSON:提取
alg(签名算法)和其他元数据(如kid)。
4. 验证签名
-
重新计算签名:
- 拼接解码后的 Header 和 Payload(
encodedHeader.encodedPayload)。 - 使用 Header 中的
alg算法和服务端保存的密钥(如JwtKey)计算新签名。
- 拼接解码后的 Header 和 Payload(
-
对比签名:
- 将计算的新签名与 JWT 中的第三部分(
encodedSignature)比较。 - 若匹配,则 签名有效(数据未被篡改且由可信方签发)。
- 将计算的新签名与 JWT 中的第三部分(
5. 验证 Claims
检查 Payload 中的标准声明(jwt.StandardClaims):
ExpiresAt:令牌是否过期?NotBefore:令牌是否已生效?Issuer:签发者是否可信?Audience:接收者是否匹配?
对应一下代码
setToken, _ := jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return JwtKey, nil
})
这段代码对应这上面的第2,3,4点
if key, ok := setToken.Claims.(*MyClaims); ok && setToken.Valid {
return key, errmsg.SUCCSE
} else {
return nil, errmsg.ERROR
}
这段代码对应着第五点