鉴权中间件JWT | 青训营笔记

295 阅读3分钟

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

前言

鉴权 验证用户是否拥有访问系统的权利。

Why? 对用户进行鉴权,防止非法用户占用网络资源,非法用户接入网络,被骗取关键信息。

Token鉴权

  1. 用户发起登陆,客户端提交用户名和密码,服务端会对用户提交的数据进行校验,校验通过以后此时会提取用户关键信息(比如用户名密码、用户id、时间戳等信息进行排序、然后再进行签名运算)通过特定的算法生成token放在响应头里面返回给客户端。
  2. 客户端收到响应结果,然后从响应结果里面获取token,然后将token放在请求头token字段中再去请求其他接口。
  3. 服务端再次收到客户端,此时服务端会去请求头中取出token,然后对token进行解析,获取到用户关键信息,再去数据库去进行查询是否存在此用户。如果token被篡改,就无法正确解析。
  4. 服务端鉴权成功就会返回对应接口数据给客户端,鉴权失败则不会返回对应数据。

介绍

JSON Web Token(JSON Web令牌)是一个开放标准(RFC 7519),它定义了一种方式,用于在各方之间安全地将信息作为 JSON 对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对对 JWT 进行签名。JSON Web Tokens - jwt.io

JSON Web Token由三部分组成,它们之间用圆点.连接。这三部分分别是:

  • Header
  • Payload
  • Signature

因此,一个典型的JWT看起来是这个样子的:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMiwiaXNzIjoidGlrdG9rX2RlbW8iLCJleHAiOjE2NzU3NzQ5OTEsIm5iZiI6MTY3NTY4ODU5MSwiaWF0IjoxNjc1Njg4NTkxfQ.hbiVLUdjz4gLXEQR81PnR_a36Kxzn430ttjwh0iVFK4

目前go使用的JWT主要是dgrijalva/jwt-gogolang-jwt/jwt

生成token

使用 jwt-go 库生成 token,我们需要定义需求(claims),也就是说我们需要通过 jwt 传输的数据。假如我们需要传输 ID 和 Username,我们可以定义 Claims 结构体,其中包含 ID 和 Username 字段,还有在 jwt-go 包预定义的 jwt.StandardClaims

dgrijalva/jwt-go定义如下

type Claims struct {
    ID       int64
    Username string 
    jwt.StandardClaims
}

其中jwt.StandardClaims包含

type StandardClaims struct {
    Audience    string  `json:"aud,omitempty"`
    ExpiresAt   int64   `json:"exp,omitempty"`
    Id          string  `json:"jti,omitempty"`
    IssuedAt    int64   `json:"iat,omitempty"`
    Issuer      string  `json:"iss,omitempty"`
    NotBefore   int64   `json:"nbf,omitempty"`
    Subject     string  `json:"sub,omitempty"`
}

其中 ExpiresAtIssuer 分别是过期时间和签发者。 使用 jwt-go 库根据指定的算法生成 jwt token ,主要用到两个方法:

  1. jwt.NewWithClaims

func jwt.NewWithClaims(method jwt.SigningMethod, claims jwt.Claims) *jwt.Token

参数 1 是 jwt.SigningMethod,其中包含三种 crypto.Hash 加密算法的方案

  • jwt.SigningMethodHS256
  • jwt.SigningMethodHS384
  • jwt.SigningMethodHS512

参数 2 是 Claims,包含自定义类型和 StandardClaimStandardClaim 嵌入在自定义类型中,以方便对标准声明进行编码,解析和验证。

  1. SignedString

func (*jwt.Token).SignedString(key interface{}) (string, error)根据传入的空接口类型参数 key,返回完整的签名令牌


golang-jwt/jwt中有所不同,如下

type Claims struct {
    ID       int64
    Username string 
   jwt.RegisteredClaims
}

解析token

使用 jwt-go 库解析 token,主要用到两个方法,分别用通过与解析传入的 token 字符串,和根据 Claims 结构体定义的相关属性要求进行校验。

jwtToken, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
   return []byte(SecretKey), nil
})

此外还要对token是否过期进行校验

dgrijalva/jwt-go类库存在安全隐患

Github的自动检测工具提示dgrijalva/jwt-go类库存在安全风险,提示更新版本,采用golang-jwt/jwt

jwt-go allows attackers to bypass intended access restrictions in situations with []string{} for m["aud"] (which is allowed by the specification). Because the type assertion fails, "" is the value of aud. This is a security problem if the JWT token is presented to a service that lacks its own audience check. There is no patch available and users of jwt-go are advised to migrate to golang-jwt at version 3.2.1