Go语言实战 : API服务器 (8) JWT

身份验证

在第一次登录之后产生一个有一定有效期的 token,并将其存储于浏览器的 Cookie 或 LocalStorage 之中,之后的请求都携带该 token ,请求到达服务器端后,服务器端用该 token 对请求进行鉴权。

更简单的方法是,直接用密钥对用户信息和时间戳进行签名对称加密,这样就可以省下额外的存储,也可以减少每一次请求时对数据库的查询压力。这种方式,在业界已经有一种标准的实现方式,该方式被称为 JSON Web Token

JWT

在 JWT 中,Token 有三部分组成,中间用 . 隔开,并使用 Base64 编码:

  • header

  • payload

  • signature

header

JWT Token 的 header 中,包含两部分信息:

  • Token 的类型
  • Token 所使用的加密算法

例如

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

payload

放置token的具体内容,可以放一些标准的字段和一些额外的字段,标准字段有:

  • iss:JWT Token 的签发者
  • sub:主题
  • exp:JWT Token 过期时间
  • aud:接收 JWT Token 的一方
  • iat:JWT Token 签发时间
  • nbf:JWT Token 生效时间
  • jti:JWT Token ID

Signature

通过前面两部分生成,生成方法:

  1. 用 Base64 对 header.payload 进行编码
  2. 用 Secret(Secret 相当于一个密码,存储在服务端) 对编码后的内容进行加密,加密后的内容即为 Signature

根据ID,用户名生成token

func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) {
	// Load the jwt secret from the Gin config if the secret isn't specified.
	if secret == "" {
		secret = viper.GetString("jwt_secret")
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"id":       c.ID,
		"username": c.Username,
		"nbf":      time.Now().Unix(),
		"iat":      time.Now().Unix(),
	})
	tokenString, err = token.SignedString([]byte(secret))
	return
}
复制代码

将请求头里面的token提取出来

func ParseRequest(c *gin.Context) (*Context, error) {
	header := c.Request.Header.Get("Authorization")

	secret := viper.GetString("jwt_secret")

	if len(header) == 0 {
		return &Context{}, ErrMissingHeader
	}

	var t string
	fmt.Sscanf(header, "Bearer %s", &t)
	return Parse(t, secret)
}
复制代码

检查secret的格式

func secretFunc(secret string) jwt.Keyfunc {
	return func(token *jwt.Token) (interface{}, error) {
		// Make sure the `alg` is what we except.
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, jwt.ErrSignatureInvalid
		}
		return []byte(secret), nil
	}
}
复制代码

解析token

func Parse(tokenString string, secret string) (*Context, error) {
	ctx := &Context{}
	token, err := jwt.Parse(tokenString, secretFunc(secret))
	if err != nil {
		return ctx, err
	} else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		ctx.ID = uint64(claims["id"].(float64))
		ctx.Username = claims["username"].(string)
		return ctx, nil
	} else {
		return ctx, err
	}
}
复制代码

测试

用户登录

image.png

不带token访问

image.png

携带token访问

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享