Gin整合Jwt-基础token操作 | 青训营笔记

250 阅读4分钟

这是我参与「第五届青训营」笔记创作活动的第 1 天

快速上手

安装jwt-go依赖:go get github.com/dgrijalva/jwt-go

原理简介

背景

HTTP协议是一种无状态的协议,这意味着用户提供账号和密码进行登录认证后,下次再请求的时候,仍然需要认证,因为服务器并不知道是谁发送的请求,并不知道该用户已经认证过一次。

所以为了解决这一问题,保持客户端与服务端的会话状态,在服务器的缓存中需要为每位用户分配一份存储空间,用于存储用户的个人登录信息等,且每份存储空间有个唯一标识ID作为自己的身份证;

这样在作响应的时候,将该ID返回给浏览器,浏览器存储到本地,以便后续再次请求时都可以携带着这个ID,服务器就能根据这个ID去对应缓存空间中去查找是否存在对应的存储区,能找到则表示该用户之前已经访问过了,存储区存储的登录信息等也可以直接使用,就不用再次登录了。

这就是传统的基于session+cookie的会话保持技术。session是存在于服务端的一个缓存区,相当于一个存储数据的map数据结构,每个session对应着自己的sessionId;而cookie则是存在于客户端浏览器的一种数据存储区。

但是这种基于Session的会话保持技术存在很多弊端,如:

  • 随着用户增加服务器的开销也会增大;
  • 在处理分布式应用的情境下会相应的限制负载均衡器的能力;
  • Cookie存储在客户端,如果被拦截窃取,会很容易受到CSRF跨域伪造请求攻击;
  • 除浏览器之外的其他设备对Cookie的支持并不友好,对跨平台支持不好;

概述

JWT全称Json Web Token,翻译为JSON格式的网络令牌,很多时候又简称为Token,它要解决的问题,就是为多种终端设备,提供统一的、安全的令牌格式。因此,JWT只是一个令牌格式而已,你可以把它存储到Cookie,也可以存储到localstorage,没有任何限制!同样的,对于传输,你可以使用任何传输方式来传输JWT,一般来说,我们会使用HTTP消息头来传输它。比如,当登录成功后,服务器可以给客户端响应一个JWT。

组成

为了保证令牌的安全性,JWT令牌由三个部分组成,分别是:

  • header:令牌头部,记录了整个令牌的类型和签名算法
  • payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里
  • signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改

Token操作

定义Claim及属性

claim 即 jwt 的载荷(payload)

Userame 属性为我们自定义的载荷,而 jwt.StandardClaims 则为 jwt 自带的属性

type myClaims struct {
   Username string `json:"username"`
   jwt.StandardClaims
}

而StandardClaims的内容如下

type StandardClaims struct {
 Audience  string `json:"aud,omitempty"`(受众,即接受 JWT 的一方)
 ExpiresAt int64  `json:"exp,omitempty"`(所签发的JWT的过期时间)
 Id        string `json:"jti,omitempty"`(JWT的Id)
 IssuedAt  int64  `json:"iat,omitempty"`(签发时间)
 Issuer    string `json:"iss,omitempty"`(JWT的签发者)
 NotBefore int64  `json:"nbf,omitempty"`(JWT的生效时间)
 Subject   string `json:"sub,omitempty"`(主题)
}

生成Token

在生成Token前需要对Claim实例化,由于需要加密,因此还需要定义一个密钥。

mySigningKey := []byte("1425723960@qq.com")
c := myClaims{
   Username: "LiuBing",
   StandardClaims: jwt.StandardClaims{
      NotBefore: time.Now().Unix() - 5, //(JWT的生效时间)
      ExpiresAt: time.Now().Unix() + 5, //(所签发的JWT的过期时间)
      Issuer:    "BingBing",
   },
}

NewWithClaims方法的第一个参数是加密算法的类型,第二个参数是Claim实例

//返回Token(未加密)
token1 := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
//对Token进行加密
secret, err := token1.SignedString(mySigningKey)
if err != nil {
   fmt.Printf("%s\n", err)
}

解析Token

//接收到前端传来的secret密文
token2, err := jwt.ParseWithClaims(secret, &myClaims{}, func(token *jwt.Token) (interface{}, error) {
   return mySigningKey, nil
})
if err != nil {
   fmt.Printf("%s\n", err)
}
fmt.Println(token2)
fmt.Println(token2.Claims)
fmt.Println(token2.Claims.(*myClaims).Username)

运行结果如下

image.png