介绍
-
以前记录python 项目的时候,就已经有过相关的 session,cookie,jwt token的笔记了。 这里再来记录go的使用。
- 其中 采用在请求头当中添加 Authorization 字段。HTTP Bearer认证
- Access Token与Refresh Token 实现双刷认证的操作:
-
jwt-go:
- 官方介绍:A go (or ‘golang’ for search engine friendliness) implementation of JSON Web Tokens
- 意思是 go 语言实现的 json web token。
- github地址:https://github.com/dgrijalva/jwt-go
- 可以查看这篇讲解:https://www.bilibili.com/video/BV14p4y1x7kF?from=search&seid=2694406004028845998&spm_id_from=333.337.0.0
-
查看文档,官方给了好几个例子:
- 可以使用这个方法,NewWithClains(CustomclaimsType)。 一般Go语言的方法命名习惯,都是这种 with … 说明是附加的可以使用相关的参数New。
- func NewWithClaims(method SigningMethod, claims Claims) Token
-
需要两个参数:
- 第一个参数是一个加密的方法
- 第二个参数是 一个接口: Claims
- type Claims
- 下面这个方法实现了这个接口:是一个map类型。
// Claims type that uses the map[string]interface{} for JSON decoding // This is the default claims type if you don't supply one type MapClaims map[string]interface{}
- 还有
-
上面俩一个是 结构体形式,一个是map形式。都可以使用。
-
公有字段:jwt的 payload
-
点击链接也可以查看:https://tools.ietf.org/html/rfc7519#section-4.1 这个标准下。
// Structured version of Claims Section, as referenced at // https://tools.ietf.org/html/rfc7519#section-4.1 // See examples for how to use this with your own claim types 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"` }
-
-
点击官方的方法介绍: 使用自定义类型声明令牌的示例。
// 定义加密 秘钥 mySigningKey := []byte("AllYourBase") // 自定义结构体数据, 并继承 内置的,标准的一些数据参数 type MyCustomClaims struct { Foo string `json:"foo"` jwt.StandardClaims // 标准自带参数 } // Create the Claims claims := MyCustomClaims{ "bar", // 添加过期时间以及签发人 jwt.StandardClaims{ // 过期时间 ExpiresAt: 15000, Issuer: "test", }, } // 生成令牌token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 加密 ss, err := token.SignedString(mySigningKey) fmt.Printf("%v %v", ss, err)
解密
-
还是开头的Examples里面, 看到下面这里: ParseWithClaims 方法
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error)
- 其中 Keyfunc
- 中文解释: 解析方法使用此回调函数提供密钥以进行验证。函数接收已解析但未验证的令牌。这允许您使用令牌头中的属性(例如’kid’)来标识要使用的密钥。
- 其中 Keyfunc
-
然后可以直接看官方的例子了:
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c" type MyCustomClaims struct { Foo string `json:"foo"` jwt.StandardClaims } // sample token is expired. override time so it parses as valid token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { // 只返回加密的秘钥 return []byte("AllYourBase"), nil }) if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt) } else { fmt.Println(err) }
-
解析的过程中,如果key过期或者是 相关信息 和加密时候不一样,那么就会报错了,自动解析时,进行验证。
-
对于使用解析出来的 token, 上面例子用已经有了
- claims, ok := token.Claims.(*MyCustomClaims)。断言一下,变为自定义的类型,然后就可以读取其中的数值了。
gin项目 中使用
-
利用中间件,做一个拦截器。接收验证token信息。
-
token 是前端放入 header 请求头中携带过来的。
- token 有三种存放方式: 第一种是存放在 header 请求头当中,第二种是放到请求体当中,第三种就是放入到 URI 中。
-
具体可以看开头给出的视频,最后部分讲解的实际项目中应用~。
-
那么在我自己的小项目中,其实就是示例上的那种简单应用,生成token和解析token
package jwt import ( "errors" "time" "github.com/golang-jwt/jwt" ) // 定义 token 生效时间 const TokenExpireOuration = time.Hour * 2 // 定义秘钥 var mySecret = []byte("这是加密的字符串") // 定义自己的 载体,并继承共有字段, 实现Claims接口 type MyCustomClaims struct { UserID int64 `json:"user_id"` Username string `json:"username"` jwt.StandardClaims } // 封装生成Token的函数 func GenToken(userID int64, username string) (string, error) { // Create the Claims claims := MyCustomClaims{ userID, username, jwt.StandardClaims{ // 指定过期时间 ExpiresAt: time.Now().Add(TokenExpireOuration).Unix(), // 签发人: 写上项目名 Issuer: "HdlBluebell", }, } // 创建token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 使用指定的secret签名并获得完整的编码后的字符串token tokenStr, err := token.SignedString(mySecret) return tokenStr, err } // 解析token字符串 func ParseToken(tokenString string) (*MyCustomClaims, error) { // 调用解析函数 token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { // 只返回加密的秘钥 return []byte("AllYourBase"), nil }) if err != nil { return nil, err } // 拿到 MyCustomClaims 类型的结构 // Claims 接口实现了 Valid() 方法, 对数据进行验证 if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid { return claims, nil } else { return nil, errors.New("invalid token") } }
-
采用 HTTP Bearer认证, https://www.cnblogs.com/qtiger/p/14868110.html这篇文章开头给出过。
-
这种方式在 Postman 当中进行测试的时候,也有对应的:
-
定义 JWT 认证中间件:
- 其中的controllers.ResponseError(c, controllers.CodeNeedLogin) 是返回请求的 c.JSON() 只是被封装了一下,代码更加的简洁。
package main import ( "bluebell/controllers" "bluebell/pkg/jwt" "strings" "github.com/gin-gonic/gin" ) // 当认证通过后, 保存到当前的定义的上下文信息当中去 const ContextUserIDKey = "userID" func JWTAuthMiddleware() func(c *gin.Context) { return func(c *gin.Context) { // HTTP Bearer: https://www.cnblogs.com/qtiger/p/14868110.html#autoid-3-4-0 // 在 http 请求头当中添加 Authorization: Bearer (token) 字段完成验证 // 1. 获取请求头中的 Authorization 认证字段信息 authStr := c.Request.Header.Get("Authorization") // 2. if authStr == "" { controllers.ResponseError(c, controllers.CodeNeedLogin) // 停止后序的 handler 处理函数执行,并终止当前后续操作 c.Abort() return } // 切分, 格式不对,报错 p := strings.Split(authStr, " ") if len(p) != 2 || p[0] != "Bearer" { controllers.ResponseError(c, controllers.CodeInvalidToken) } // 3. 解析token m, err := jwt.ParseToken(p[1]) if err != nil { controllers.ResponseError(c, controllers.CodeInvalidToken) c.Abort() return } // 保存当前登录的用户信息,存入context 上下文当中去,并继续向下执行 c.Set(ContextUserIDKey, m.UserID) c.Next() } }
下面采用 Access Token, Refresh Token 双刷,实现 认证。
-
可以参考这篇文章中的内容: https://blog.csdn.net/a704397849/article/details/90216739
-
Access Token: 访问后端接口的请求 token。为了安全性,有效期通常较短, 因为只要在有效期内获取token, 都可以对后端资源进行请求访问。
-
Refresh Token: 有效期较长。 当 Access Token 过期的时候, 用Refresh Token 就可以获取到新的 Access Token。保证了用户的体验性和安全性。不需要频繁的 token过期而 重新登录。 只有在 Refresh Token 过期的时候,才会去重新进行登录的操作。
-
生成 refresh token
// 生成 refresh token, 不需要自定义的信息 rToken, err = jwt.NewWithClaims(jwt.SigningMethodES256, jwt.StandardClaims{ ExpiresAt: time.Now().Add(time.Second * 30).Unix(), Issuer: "HdlBluebell", }).SignedString(mySecret)
-
另外就是解析时候判断access token 是否有效。refresh token 刷新 access token。