使用JWT包生成token

详情看代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 记得引入jwt包
// import "github.com/dgrijalva/jwt-go"

// 这里定义的字段其实就是生成token串中的属性
type JwtCustomClaims struct {
    jwt.StandardClaims // 包中自带的默认属性

    Uid  uint `json:"uid"` // 当前访问人uid 自定义添加一些自己需要的元素
}

// 生成token串
// 这里的参数一般为自己自定义的一些参数
func GenerateToken(userId uint, role int8) (string, error) {
    // 这里使用的是自定义结构体的方式床架claims,也可以使用jwt.MapClaims结构体
    /*
        claims := jwt.MapClaims{} // 其实就是个map
        claims["uid"] = userId
    */
    claims := JwtCustomClaims{
        jwt.StandardClaims{
            ExpiresAt: int64(time.Now().Add(time.Minute * time.Duration(expire)).Unix()),
            Issuer:    config.GetConfig().Encrypt.EncryptIssuer,
        },
        userId,
        role,
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    // 这里需要注意当使用jwt.SigningMethodHS256方式生成token串时,SignedString方法的参数应该是[]byte数组,其他方式也对key有着要求
    tokenStr, err := token.SignedString([]byte(config.GetConfig().Encrypt.EncryptKey))
    if err != nil {
        logrus.Errorf("[GenerateToken] generate token error,err_msg = %s", err.Error())
        return "", err
    }

    return tokenStr, nil
}

// 解析token串数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 解析token串
func ParseToken(tokenStr string) (cliams jwt.MapClaims, err error) {
    // 创建对象,直接调用parse方法
    var token *jwt.Token
    token, err = jwt.Parse(tokenStr, func(*jwt.Token) (interface{}, error) {
        // 注意这里也是[]byte数组
        return []byte(config.GetConfig().Encrypt.EncryptKey), nil
    })
    if err != nil {
        logrus.Errorf("[ParseToken] parse token error,err_msg = %s", err.Error())
        return
    }

    // 获取jwt.Token对象后,获取定义的claims
    var ok bool
    // 注意 生成token串时无论是用自定义结构体的方式还是直接使用jwt.MapClaims,这里断言结果都为jwt.MapClaims(断言为自定义的结构体会失败)
    // 断言获取到的jwt.MapClaims实际上为map[string]interface{},使用key获取值时,需要再做一次类型断言,需要注意类型的转换,数值类型会被转化为float64(设置map["uid"] = 10,获取到的map["uid"]实际为float64类型的10.00000)
    cliams, ok = token.Claims.(jwt.MapClaims)
    if !ok {
        logrus.Errorf("[ParseToken]token claims type error,can't convert")
        return cliams, fmt.Errorf("token claims can't convert to JwtCustomerClaims")
    }

    return
}

使用Gin中间件拦截验证token串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func TokenAuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
    // 这里需要根据前端传递token传的方式使用不同的方式获取
    // 放在请求头(Request Header),使用c.Request.Header.Get("[前端设置的key]") 【注意将自定义的key放在header中时,后端是否进行了跨域资源访问的控制】
    // 放在get请求链接中或post请求的参数中,使用c.Query("[前端设置的key]")
        tokenStr := c.Request.Header.Get("api_token")
        if tokenStr == "" {
            // 尝试从链接中获取 兼容下部分特殊get请求
            tokenStr = c.Query("api_token")
            if tokenStr == "" {
                c.JSON(http.StatusUnauthorized, gin.H{
                    "success": "false",
                    "msg":     "用户未登陆",
                })
                c.Abort()
                return
            }
        }

        // 解析token
        claims, err := token.ParseToken(tokenStr)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{
                "success": "false",
                "msg":     "token 解析失败",
            })
            c.Abort()
            return
        }
        // 注意这里数值类型断言后的类型位float64
        c.Set("uid", int(claims["uid"].(float64)))
        c.Next()
    }
}

Gin跨域资源访问控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func Cors() gin.HandlerFunc {
    return func(c *gin.Context) {
        method := c.Request.Method               //请求方法
        origin := c.Request.Header.Get("Origin") //请求头部
       
        if origin != "" {
            c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
            c.Header("Access-Control-Allow-Origin", "*")                                       // 这是允许访问所有域
            c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
            // 浏览器请求时,Request Header中允许使用所有key
            c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,api_token")
            c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析
            c.Header("Access-Control-Max-Age", "172800")                                                                                                                                                           // 缓存请求信息 单位为秒
            c.Header("Access-Control-Allow-Credentials", "false")                                                                                                                                                  //  跨域请求是否需要带cookie信息 默认设置为true
            c.Set("content-type", "application/json")                                                                                                                                                              // 设置默认返回格式是json
        }

        //放行所有OPTIONS方法 原因为当Request Header中存在自定义的key时,浏览器会首先发送一个options请求,判断后端是否支持,只有返回状态码为200时,才会真正发送请求
        if method == "OPTIONS" {
            c.JSON(http.StatusOK, "Options Request!")
        }
        // 处理请求
        c.Next()
    }
}