我们之前使用session和cookies来进行身份验证,但是cookies是浏览器的产物,它有局限性,无法适用多个不同平台的客户端,JWT可以很好的解决这种问题。

1. JWT是什么

JWT即Json Web Token,用于服务端身份验证;

整个JWT分为三分部,使用.号连接组成

  • Header
  • Payload
  • Signature

Hedaer和Payload需要进行base64编码,使用.连接Header和Payload进行哈希加密得到Signature

  1. Header: {
  2. "alg": "HS256", // 摘要算法,保证header和payload不被篡改
  3. "typ": "jwt" // token类型
  4. }
  5. // base64编码:ewogICJhbGciOiAiSFMyNTYiLCAKICAidHlwIjogImp3dCIKfQ==
  6. Payload: {
  7. "userid": 1,
  8. "username": "zhangsan",
  9. "iss": "cyj19", // 颁发者
  10. "iat": "1654395001", // 颁发时间
  11. "exp": "1654310111" // 过期时间
  12. }
  13. // base64编码:ewogICJ1c2VyaWQiOiAxLAogICJ1c2VybmFtZSI6ICJ6aGFuZ3NhbiIsCiAgImlzcyI6ICJjeWoxOSIsIAogICJpYXQiOiAiMTY1NDM5NTAwMSIsIAogICJleHAiOiAiMTY1NDMxMDExMSIgCn0=
  14. /*
  15. ewogICJhbGciOiAiSFMyNTYiLCAKICAidHlwIjogImp3dCIKfQ==.ewogICJ1c2VyaWQiOiAxLAogICJ1c2VybmFtZSI6ICJ6aGFuZ3NhbiIsCiAgImlzcyI6ICJjeWoxOSIsIAogICJpYXQiOiAiMTY1NDM5NTAwMSIsIAogICJleHAiOiAiMTY1NDMxMDExMSIgCn0=
  16. 进行HS256加密后得到:53ba6671038c3777db4f3abad2eb412bda391c51231073a4abcdaf721a0c1f8a
  17. */
  18. Signature: 53ba6671038c3777db4f3abad2eb412bda391c51231073a4abcdaf721a0c1f8a

经过上面的编码加密,我们得到token: ewogICJhbGciOiAiSFMyNTYiLCAKICAidHlwIjogImp3dCIKfQ==.ewogICJ1c2VyaWQiOiAxLAogICJ1c2VybmFtZSI6ICJ6aGFuZ3NhbiIsCiAgImlzcyI6ICJjeWoxOSIsIAogICJpYXQiOiAiMTY1NDM5NTAwMSIsIAogICJleHAiOiAiMTY1NDMxMDExMSIgCn0=.53ba6671038c3777db4f3abad2eb412bda391c51231073a4abcdaf721a0c1f8a
那么服务端是如何进行身份验证的呢?其实服务端主要是验证token信息的有效性,通过取出token的Header和Payload部分,使用同样的哈希算法生成摘要信息Signature,然后与token中的摘要信息进行比对即可。

2. 实践

我们使用github.com/golang-jwt/jwt/v4库进行开发

创建生成token和检验token的方法

  1. type CustomClaims struct {
  2. AppKey map[string]interface{} `json:"app_key"`
  3. jwt.RegisteredClaims
  4. }
  5. func GetSecret(secret string) []byte {
  6. return []byte(secret)
  7. }
  8. func GenerateToken(appKey map[string]interface{}, secret, issuer string, expire time.Duration) (string, error) {
  9. nowTime := time.Now()
  10. expireTime := nowTime.Add(expire)
  11. claims := CustomClaims{
  12. AppKey: appKey,
  13. RegisteredClaims: jwt.RegisteredClaims{
  14. ExpiresAt: jwt.NewNumericDate(expireTime),
  15. Issure: issuer,
  16. IssuedAt: jwt.NewNumericDate(nowTime),
  17. },
  18. }
  19. tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  20. // 使用hmac算法,参数必须是[]byte
  21. return tokenClaims.SignedString([]byte(secret))
  22. }
  23. func ParseToken(token, secret string) (*CustomClaims, error) {
  24. tokenClaims, err := jwt.ParseWithClaims(token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
  25. return GetSecret(secret), nil
  26. })
  27. if err != nil {
  28. return nil, err
  29. }
  30. if tokenClaims != nil {
  31. claims, ok := tokenClaims.Claims.(*CustomClaims)
  32. // 检验token
  33. if ok && tokenClaims.Valid {
  34. return claims, nil
  35. }
  36. }
  37. return nil, err
  38. }

创建JWT中间件

  1. import (
  2. "github.com/gin-gonic/gin"
  3. "github.com/golang-jwt/jwt/v4"
  4. "strings"
  5. )
  6. func JWT() gin.HandleFunc {
  7. return func(c *gin.Context) {
  8. // 从请求头的Authorization中获取token,格式为"Bearer token"
  9. authorization := c.Request.Header.Get("Authorization")
  10. if authorization == "" {
  11. c.Abort()
  12. c.Writer.Write([]byte("token 为空"))
  13. return
  14. }
  15. // 去除前后两段空格
  16. authorization = strings.TrimSpace(authorization)
  17. // 根据空格分割
  18. parts := strings.SplitN(authorizaton, " ", 2)
  19. if len(parts) != 2 || parts[0] != "Bearer" {
  20. c.Abort()
  21. c.Writer.Write([]byte("无法获取token"))
  22. return
  23. }
  24. // 检验token
  25. claim, err := ParseToken(token, "go-secret")
  26. if err != nil {
  27. str := "token鉴权失败"
  28. if err.(*jwt.ValidationError).Errors == jwt.ValidationErrorExpired {
  29. str = "token已过期"
  30. }
  31. c.Abort()
  32. c.Writer.Write([]byte(str))
  33. return
  34. }
  35. c.Set("sys_user", claim.AppKey)
  36. c.Next()
  37. }
  38. }