Gin(十二):配合JWT

文章首发于 我的博客 ISLAND 和 我的微信公众号 代码猎奇站前端

在先后端分离的项目中,愈来愈多的项目采用 JWT 代替传统的 cookie ,这里咱们来使用 JWT 结合 Gin 来做为一个登陆受权和权限校验。mysql

🔑什么是 JWT

JWT 的全称叫作 JSON WEB TOKEN,在目前先后端系统中使用较多。git

JWT 构成

JWT 是由三段构成的。分别是 HEADER,PAYLOAD,VERIFY SIGNATURE,它们生成的信息经过 . 分割。github

HEADER

header 是由 一个 typalg 组成,typ 会指明为 JWT,而 alg 是所使用的加密算法。算法

{
  "alg": "HS256",
  "typ": "JWT"
}
复制代码

PAYLOAD

payload 是 JWT 的载体,也就是咱们要承载的信息。这段信息是咱们能够自定义的,能够定义咱们要存放什么信息,那些字段。该部分信息不宜过多,它会影响 JWT 生成的大小,还有就是请勿将敏感数据存入该部分,该端数据前端是能够解析获取 token 内信息的。sql

官方给了七个默认字段,咱们能够不所有使用,也能够加入咱们须要的字段。json

名称 含义
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 是否正确,若是正确则正确执行该请求,否者验证失败,从新登陆。

🔒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的生成。

🔐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
复制代码

✍总结

本章节对什么是 JWT,Gin 中如何使用 JWT 作了介绍。可是不要过于迷信 JWT,JWT 还有不少问题,好比说 JWT 失效只能是时间过时,若是修改密码或者帐户注销等操做须要咱们另外添加逻辑判断。适合的地方选用适合的技术才能发挥最大的优点。

👨‍💻本章节代码

Github

历史文章

Gin(一):Hello
Gin(二):路由Router
Gin(三):模板tmpl
Gin(四):表单提交校验和模型绑定
Gin(五):链接MySQL
Gin(六):文件的上传
Gin(七):中间件的使用和定义
Gin(八):Cookie的使用
Gin(九):生成restful接口
Gin(十):集成 Swagger
Gin(十一)集成ORM-gorm
Gin(十二)集成JWT

相关文章
相关标签/搜索