Gin(十二):配合JWT
文章首发于 个人博客 ISLAND 和 个人微信公众号 代码猎奇站
在前后端分离的项目中,越来越多的项目采用 JWT
代替传统的 cookie
,这里我们来使用 JWT
结合 Gin
来作为一个登录授权和权限校验。
:key:什么是 JWT
JWT 的全称叫做 JSON WEB TOKEN,在目前前后端系统中使用较多。
JWT 构成
JWT 是由三段构成的。分别是 HEADER,PAYLOAD,VERIFY SIGNATURE,它们生成的信息通过 .
分割。

HEADER
header 是由 一个 typ
和 alg
组成, typ
会指明为 JWT,而 alg
是所使用的加密算法。
{ "alg": "HS256", "typ": "JWT" } 复制代码
PAYLOAD
payload 是 JWT 的载体,也就是我们要承载的信息。这段信息是我们可以自定义的,可以定义我们要存放什么信息,那些字段。该部分信息不宜过多,它会影响 JWT 生成的大小,还有就是请勿将敏感数据存入该部分,该端数据前端是可以解析获取 token 内信息的。
官方给了七个默认字段,我们可以不全部使用,也可以加入我们需要的字段。
名称 | 含义 |
---|---|
Audience | 表示JWT的受众 |
ExpiresAt | 失效时间 |
Id | 签发编号 |
IssuedAt | 签发时间 |
Issuer | 签发人 |
NotBefore | 生效时间 |
Subject | 主题 |
VERIFY SIGNATURE
这也是 JWT 的最后一段,该部分是由算法计算完成的。
对刚刚的 header 进行 base64Url 编码,对 payload 进行 base64Url 编码,两端完成编码后通过 .
进行连接起来。
base64UrlEncode(header).base64UrlEncode(payload) 复制代码
完成上述步骤后,就要通过我们 header 里指定的加密算法对上部分进行加密,同时我们还要插入我们的一个密钥,来确保我的 JWT 签发是安全的。
这便是我们的第三部分。
当三部分都完成后,通过使用 .
将三部分分割,生成了上图所示的 JWT 。
JWT 登录原理
简单的说就是当用户登录的时候,服务器校验登录名称和密码是否正确,正确的话,会生成 JWT 返回给客户端。客户端获取到 JWT 后要进行保存,之后的每次请求都会讲 JWT 携带在头部,每次服务器都会获取头部的 JWT 是否正确,如果正确则正确执行该请求,否者验证失败,重新登录。

:lock:Gin 生成 JWT
go 语言的 JWT 库有很多。 jwt.io 上也给出了很多 。这里使用 jwt-go
"github.com/dgrijalva/jwt-go"
我们对登录方法进行改造。
// 省略代码 expiresTime := time.Now().Unix() + int64(config.OneDayOfHours) claims := jwt.StandardClaims{ Audience: user.Username, // 受众 ExpiresAt: expiresTime, // 失效时间 Id: string(user.ID), // 编号 IssuedAt: time.Now().Unix(), // 签发时间 Issuer: "gin hello", // 签发人 NotBefore: time.Now().Unix(), // 生效时间 Subject: "login", // 主题 } var jwtSecret = []byte(config.Secret) tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 省略代码 复制代码
这里的 config.OneDayOfHours
设定了过期时间,这里设定了一天。通过 StandardClaims
生成标准的载体,也就是上文提到的七个字段,其中 编号设定为 用户 id。其中的 jwtSecret
是我们设定的密钥,
我们这里通过 HS256 算法生成 tokenClaims
,这就是我们的 HEADER 部分和 PAYLOAD。
token, err := tokenClaims.SignedString(jwtSecret) 复制代码
这样便生成了我们的 token 。我们要将我们的 token 和 Bearer 拼接在一起,同时中间用空格隔开。
token = "Bearer "+ token 复制代码
生成 Bearer Token 。
当我们用户进行登录的时候,就可以通过该片段生成 JWT。
下面是完整代码:
func CreateJwt(ctx *gin.Context) { // 获取用户 user := &model.User{} result := &model.Result{ Code: 200, Message: "登录成功", Data: nil, } if e := ctx.BindJSON(&user); e != nil { result.Message = "数据绑定失败" result.Code = http.StatusUnauthorized ctx.JSON(http.StatusUnauthorized, gin.H{ "result": result, }) } u := user.QueryByUsername() if u.Password == user.Password { expiresTime := time.Now().Unix() + int64(config.OneDayOfHours) claims := jwt.StandardClaims{ Audience: user.Username, // 受众 ExpiresAt: expiresTime, // 失效时间 Id: string(user.ID), // 编号 IssuedAt: time.Now().Unix(), // 签发时间 Issuer: "gin hello", // 签发人 NotBefore: time.Now().Unix(), // 生效时间 Subject: "login", // 主题 } var jwtSecret = []byte(config.Secret) tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) if token, err := tokenClaims.SignedString(jwtSecret); err == nil { result.Message = "登录成功" result.Data = "Bearer " + token result.Code = http.StatusOK ctx.JSON(result.Code, gin.H{ "result": result, }) } else { result.Message = "登录失败" result.Code = http.StatusOK ctx.JSON(result.Code, gin.H{ "result": result, }) } } else { result.Message = "登录失败" result.Code = http.StatusOK ctx.JSON(result.Code, gin.H{ "result": result, }) } } 复制代码
通过 .http 请求测试,结果如下
{ "result": { "code": 200, "message": "登录成功", "data": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTY0MzksImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NjQxOSwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk2NDE5LCJzdWIiOiJsb2dpbiJ9.CpacmfBSMgmK2TgrT-KwNB60bsvwgyryGQ0pWZr8laU" } } 复制代码
这个便完成了token的生成。
:closed_lock_with_key:Gin 校验 Token
那么,接下来就需要完成 token 的验证。
还记得之前我们验证用户是否授权采用的办法吗?是的,在中间件里查看用户 cookie。同样的方法,我们这里校验用户 JWT 是否有效。
编写我们的中间件。
新建立 middleware/Auth.go
首先先编写我们的解析 token 方法, parseToken()
func parseToken(token string) (*jwt.StandardClaims, error) { jwtToken, err := jwt.ParseWithClaims(token, &jwt.StandardClaims{}, func(token *jwt.Token) (i interface{}, e error) { return []byte(config.Secret), nil }) if err == nil && jwtToken != nil { if claim, ok := jwtToken.Claims.(*jwt.StandardClaims); ok && jwtToken.Valid { return claim, nil } } return nil, err } 复制代码
通过传入我们的 token , 来对 token 进行解析。
完整的中间件代码
func Auth() gin.HandlerFunc { return func(context *gin.Context) { result := model.Result{ Code: http.StatusUnauthorized, Message: "无法认证,重新登录", Data: nil, } auth := context.Request.Header.Get("Authorization") if len(auth) == 0 { context.Abort() context.JSON(http.StatusUnauthorized, gin.H{ "result": result, }) } auth = strings.Fields(auth)[1] // 校验token _, err := parseToken(auth) if err != nil { context.Abort() result.Message = "token 过期" + err.Error() context.JSON(http.StatusUnauthorized, gin.H{ "result": result, }) } else { println("token 正确") } context.Next() } } 复制代码
首先在请求头获取 token ,然后对先把 token 进行解析,将 Bearer 和 JWT 拆分出来,将 JWT 进行校验。
我们只需要对我们需要校验的路由进行添加中间件校验即可。
router.GET("/", middleware.Auth(), func(context *gin.Context) { context.JSON(http.StatusOK, time.Now().Unix()) }) 复制代码
当我们访问 /
的时候就需要携带 token 了
GET http://localhost:8080 Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTQzNjIsImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NDM0MiwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk0MzQyLCJzdWIiOiJsb2dpbiJ9.uQxGMsftyVFtYIGwQVm1QB2djw-uMfDbw81E5LMjliU 复制代码