我们之前使用session和cookies来进行身份验证,但是cookies是浏览器的产物,它有局限性,无法适用多个不同平台的客户端,JWT可以很好的解决这种问题。
1. JWT是什么
JWT即Json Web Token,用于服务端身份验证;
整个JWT分为三分部,使用
.号连接组成
- Header
- Payload
- Signature
Hedaer和Payload需要进行base64编码,使用.连接Header和Payload进行哈希加密得到Signature
Header: {"alg": "HS256", // 摘要算法,保证header和payload不被篡改"typ": "jwt" // token类型}// base64编码:ewogICJhbGciOiAiSFMyNTYiLCAKICAidHlwIjogImp3dCIKfQ==Payload: {"userid": 1,"username": "zhangsan","iss": "cyj19", // 颁发者"iat": "1654395001", // 颁发时间"exp": "1654310111" // 过期时间}// base64编码:ewogICJ1c2VyaWQiOiAxLAogICJ1c2VybmFtZSI6ICJ6aGFuZ3NhbiIsCiAgImlzcyI6ICJjeWoxOSIsIAogICJpYXQiOiAiMTY1NDM5NTAwMSIsIAogICJleHAiOiAiMTY1NDMxMDExMSIgCn0=/*ewogICJhbGciOiAiSFMyNTYiLCAKICAidHlwIjogImp3dCIKfQ==.ewogICJ1c2VyaWQiOiAxLAogICJ1c2VybmFtZSI6ICJ6aGFuZ3NhbiIsCiAgImlzcyI6ICJjeWoxOSIsIAogICJpYXQiOiAiMTY1NDM5NTAwMSIsIAogICJleHAiOiAiMTY1NDMxMDExMSIgCn0=进行HS256加密后得到:53ba6671038c3777db4f3abad2eb412bda391c51231073a4abcdaf721a0c1f8a*/Signature: 53ba6671038c3777db4f3abad2eb412bda391c51231073a4abcdaf721a0c1f8a
经过上面的编码加密,我们得到token: ewogICJhbGciOiAiSFMyNTYiLCAKICAidHlwIjogImp3dCIKfQ==.ewogICJ1c2VyaWQiOiAxLAogICJ1c2VybmFtZSI6ICJ6aGFuZ3NhbiIsCiAgImlzcyI6ICJjeWoxOSIsIAogICJpYXQiOiAiMTY1NDM5NTAwMSIsIAogICJleHAiOiAiMTY1NDMxMDExMSIgCn0=.53ba6671038c3777db4f3abad2eb412bda391c51231073a4abcdaf721a0c1f8a
那么服务端是如何进行身份验证的呢?其实服务端主要是验证token信息的有效性,通过取出token的Header和Payload部分,使用同样的哈希算法生成摘要信息Signature,然后与token中的摘要信息进行比对即可。
2. 实践
我们使用github.com/golang-jwt/jwt/v4库进行开发
创建生成token和检验token的方法
type CustomClaims struct {AppKey map[string]interface{} `json:"app_key"`jwt.RegisteredClaims}func GetSecret(secret string) []byte {return []byte(secret)}func GenerateToken(appKey map[string]interface{}, secret, issuer string, expire time.Duration) (string, error) {nowTime := time.Now()expireTime := nowTime.Add(expire)claims := CustomClaims{AppKey: appKey,RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(expireTime),Issure: issuer,IssuedAt: jwt.NewNumericDate(nowTime),},}tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)// 使用hmac算法,参数必须是[]bytereturn tokenClaims.SignedString([]byte(secret))}func ParseToken(token, secret string) (*CustomClaims, error) {tokenClaims, err := jwt.ParseWithClaims(token, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {return GetSecret(secret), nil})if err != nil {return nil, err}if tokenClaims != nil {claims, ok := tokenClaims.Claims.(*CustomClaims)// 检验tokenif ok && tokenClaims.Valid {return claims, nil}}return nil, err}
创建JWT中间件
import ("github.com/gin-gonic/gin""github.com/golang-jwt/jwt/v4""strings")func JWT() gin.HandleFunc {return func(c *gin.Context) {// 从请求头的Authorization中获取token,格式为"Bearer token"authorization := c.Request.Header.Get("Authorization")if authorization == "" {c.Abort()c.Writer.Write([]byte("token 为空"))return}// 去除前后两段空格authorization = strings.TrimSpace(authorization)// 根据空格分割parts := strings.SplitN(authorizaton, " ", 2)if len(parts) != 2 || parts[0] != "Bearer" {c.Abort()c.Writer.Write([]byte("无法获取token"))return}// 检验tokenclaim, err := ParseToken(token, "go-secret")if err != nil {str := "token鉴权失败"if err.(*jwt.ValidationError).Errors == jwt.ValidationErrorExpired {str = "token已过期"}c.Abort()c.Writer.Write([]byte(str))return}c.Set("sys_user", claim.AppKey)c.Next()}}
