在Gin框架中使用JWT
1.Why JWT
JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。
JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。
安装:
go get github.com/golang-jwt/jwt/v4
2.基本使用
如果我们直接使用JWT中默认的字段,没有其他定制化的需求则可以直接使用这个包中的和方法快速生成和解析token
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v4"
"time"
)
// 用于签名的字符串
var mySigningKey = []byte("your-page.com")
// GenRegisteredClaims 使用默认声明创建jwt
func GenRegisteredClaims() (string, error) {
// 创建 Claims
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 过期时间
Issuer: "dahe", // 签发人
}
// 生成token对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 生成签名字符串
return token.SignedString(mySigningKey)
}
// ParseRegisteredClaims 解析jwt
func ValidateRegisteredClaims(tokenString string) bool {
// 解析token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{
}, error) {
return mySigningKey, nil
})
if err != nil {
// 解析token失败
return false
}
return token.Valid
}
func main() {
// 生成token字符串
tokenString, err := GenRegisteredClaims()
if err != nil {
fmt.Println("分发错误!")
}
fmt.Println("tokenString:" + tokenString)
// tokenString:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkYWhlIiwiZXhwIjoxNjc4ODQ1OTE1fQ.e52FiGdhMdlXUu85KCCZOfkBs4uW56LLWWc_HM5gTIA
// 解析token字符串
isTrue := ValidateRegisteredClaims(tokenString)
fmt.Println(isTrue) // true
}
3.自定义Claims
前期准备
我们需要定制自己的需求来决定JWT中保存哪些数据,比如我们规定在JWT中要存储username信息,那么我们就定义一个MyClaims结构体如下:
// CustomClaims 自定义声明类型 并内嵌jwt.RegisteredClaims
// jwt包自带的jwt.RegisteredClaims只包含了官方字段
// 假设我们这里需要额外记录一个username字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type CustomClaims struct {
// 可根据需要自行添加字段
Username string `json:"username"`
Email string `json:"email"`
jwt.RegisteredClaims // 内嵌标准的声明
}
然后我们定义JWT的过期时间,这里以24小时为例:
// jwt token 的过期时间
const TokenExpireDuration = time.Hour * 24
接下来还需要定义一个用于签名的字符串:
// CustomSecret 用于加盐的字符串
var CustomSecret = []byte("夏天夏天悄悄过去")
生成JWT
我们可以根据自己的业务需要封装一个生成 token 的函数
// GenToken 生成JWT
func GenToken(username, email string) (string, error) {
// 创建一个我们自己的声明
claims := CustomClaims{
username, // 自定义的用户名字段
email, // 自定义的邮箱字段
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireDuration)),
Issuer: "my-project", // 签发人
},
}
// 使用指定的签名方法创建签名对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用指定的secret签名并获得完整的编码后的字符串token
return token.SignedString(CustomSecret)
}
解析JWT
根据给定的 JWT 字符串,解析出数据
// ParseToken 解析JWT
func ParseToken(tokenString string) (*CustomClaims, error) {
// 解析token
// 如果是自定义Claim结构体则需要使用 ParseWithClaims 方法
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{
}, func(token *jwt.Token) (i interface{
}, err error) {
return CustomSecret, nil
})
if err != nil {
return nil, err
}
// 对token对象中的Claim进行类型断言
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
// 校验token
return claims, nil
}
return nil, errors.New("invalid token")
}
现在来测试一下吧:
func main() {
token, err := GenToken("dahe", "[email protected]")
if err != nil {
fmt.Println("授权错误!")
}
fmt.Println("token:" + token)
// token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRhaGUiLCJlbWFpbCI6IjEyM0BxcS5jb20iLCJpc3MiOiJteS1wcm9qZWN0IiwiZXhwIjoxNjc4ODQ2NzkzfQ.z4sP9Do_1eSfWvA69pJq8onIUqSpmqgJM5qO-X_4vq0
parseToken, err := ParseToken(token)
if err != nil {
fmt.Println("token解析错误!")
}
fmt.Println(parseToken)
// &{dahe [email protected] {my-project [] 2023-03-15 10:19:53 +0800 CST <nil> <nil> }}
}
4.在gin框架中使用JWT
首先我们注册一条路由/auth
,对外提供获取Token的渠道:
r.POST("/auth", authHandler)
我们的authHandler定义如下:
func authHandler(c *gin.Context) {
// 用户发送用户名和密码过来
var user UserInfo
err := c.ShouldBind(&user)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "无效的参数",
})
return
}
// 校验用户名和密码是否正确
if user.Username == "dahe" && user.Password == "admin123" {
// 生成Token
tokenString, _ := GenToken(user.Username)
c.JSON(http.StatusOK, gin.H{
"code": 2000,
"msg": "success",
"data": gin.H{
"token": tokenString},
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 2002,
"msg": "鉴权失败",
})
return
}
用户通过上面的接口获取Token之后,后续就会携带着Token再来请求我们的其他接口,这个时候就需要对这些请求的Token进行校验操作了,很显然我们应该实现一个检验Token的中间件,具体实现如下:
// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI
// 这里假设Token放在Header的Authorization中,并使用Bearer开头
// 这里的具体实现方式要依据你的实际业务情况决定
authHeader := c.Request.Header.Get("Authorization")
if authHeader == "" {
c.JSON(http.StatusOK, gin.H{
"code": 2003,
"msg": "请求头中auth为空",
})
c.Abort()
return
}
// 按空格分割
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.JSON(http.StatusOK, gin.H{
"code": 2004,
"msg": "请求头中auth格式有误",
})
c.Abort()
return
}
// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
mc, err := ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2005,
"msg": "无效的Token",
})
c.Abort()
return
}
// 将当前请求的username信息保存到请求的上下文c上
c.Set("username", mc.Username)
c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息
}
}