go如何生成jwt和如何验证

466 阅读6分钟

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 是否合法。其返回值如下:

返回值含义

返回值含义
trueJWT 完全合法: 1. 签名验证通过(密钥和算法匹配)。 2. 标准声明(如 ExpiresAtNotBefore)合法。
falseJWT 无效: 1. 签名不匹配。 2. 声明验证失败(如已过期、未生效)。 3. 其他验证错误。

这个是可以捕获具体错误的,这里就不展开了。

讲一下我所理解的jwt

1什么是jwt

JWT(JSON Web Token)是一种安全传输信息的标准格式,它把用户信息(如 ID、角色)打包成一个加密字符串,用于在客户端和服务器之间传递身份验证信息。

2 jwt的组成

组成内容
Header一个 JSON 对象,描述 JWT 的元数据
Payload一个 JSON 对象,用来存放实际需要传递的数据。可以简单理解为前面的MyClaims
SignatureSignature 部分是对前两部分的签名,通俗讲,它就是将前面两个用特定的方法编码后拼接在一起,然后和JwtKey用特定算法生成的一个东西。

3jwt的生成(jwt就理解为一个token)

  1. 生成Header和Payload

    在前面的代码中,

    下面这段代码生成了一个jwt.Token 对象,第一个参数是设定Header中的alg属性,表示签名的算法(algorithm)。(这里的签名就是指 生成 Signature 的过程)。 第二个参数就可以相当于Payload。如果想要设置Header的其他内容,就可以用类似 reqClaim.Header["typ"] = "JWT",这样的形式设置。

reqClaim := jwt.NewWithClaims(jwt.SigningMethodHS256, SetClaims)

这就完成了第一步

  1. 生成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 HeaderAuthorization: Bearer <JWT>
  • URL 参数?token=<JWT>
  • Cookietoken=<JWT>
2. 解析基本结构

服务端将 JWT 按 . 拆分为三部分:

[encodedHeader].[encodedPayload].[encodedSignature]
3. 解码 Header
  1. Base64URL 解码:将第一部分解码为 JSON 字符串。

  2. 解析 JSON:提取 alg(签名算法)和其他元数据(如 kid)。

4. 验证签名
  1. 重新计算签名

    • 拼接解码后的 Header 和 Payload(encodedHeader.encodedPayload)。
    • 使用 Header 中的 alg 算法和服务端保存的密钥(如 JwtKey)计算新签名。
  2. 对比签名

    • 将计算的新签名与 JWT 中的第三部分(encodedSignature)比较。
    • 若匹配,则 签名有效(数据未被篡改且由可信方签发)。
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
}

这段代码对应着第五点

草草完结!