gin~jwt的权限验证

go-jwt

## jwt格式

一个正确的JWT格式如下所示:

eyJhbGciOiJIUzI1NiIsInR5c.eyJ1c2VybmFtZaYjiJ9._eCVNYFYnMXwpgGX9Iu412EQSOFuEGl2c

我们看到一个JWT字符串由Header,Payload,Signature三个部分组成,中间使用逗号连接。

Header

Header是一个JSON对象,由token类型和加密算法两个部分组成的,如:

json复制代码{
  "typ": "JWT",//默认为JWT
  "alg": "HS256"//支持多种加密算法
}

将上面的JSON对象使用Base64URL算法转换成字符串,即可得到JWT中的Header部分。

注意:JWT编码并不使用Base64,而Base64Url,这是因为Base64生成字符串里,可能会有+,/和=这三个URL中特殊的符号,而我们又可能将token放在URL上传递到服务器上(如test.com?token=xxx),%EF%BC%8C) 而Base64URL算法,则是在Base64算法生成的字符串基础上,将=省略,将+替换成-,将/替换成_。

Payload

JWT的Payload部分与Header一样,也是一个JSON对象,用来存放我们实际需要的数据,JWT标准提供了七个可选的字段,分别为:

标题描述
iss(issuer)签发者,其值为大小写敏感的字符串或Uri
sub(subject)主题,用于鉴别一个用户
exp(expiration time)过期时间
aud(audience)受众
iat(issued at)签发时间
nbf(not before)生效时间
jti(JWT ID)编号

除了标准的字段外,我们可以任意定义私有的字段以满足业务需求,如:

json复制代码{
    iss:"my",//标准字段
    jti:"test",//标准字段
    username:"aaa",//自定义字段
    "gender":"男",
    "avatar":"https://1.jpg"
}

将上面的JSON对象使用Base64URL算法转换成字符串,即可得到JWT中的Payload部分。

在go的项目中使用jwt

1、使用"github.com/golang-jwt/jwt"这个库来生成jwt字符

package main

import (
	"fmt"
	"github.com/golang-jwt/jwt"
	"time"
)

func main() {
  
  //通过其中创建了一个名为 key 的字节切片,并将其初始化为包含字符串 "secret" 的字节数组
  
	key := []byte("111")
	
  /*
   jwt.NewWithClaims 函数创建了一个JWT对象。它接受两个参数:签名方法和声明(claims)。

	jwt.SigningMethodHS256 表示使用HS256算法进行JWT签名
	jwt.MapClaims 表示声明部分,其中包含了您希望在JWT中包含的数据。

	"nbf" 字段表示"not before",即JWT的生效时间,这里设置为2015年10月10日12点	00分00秒(UTC时间)的Unix时间戳。
  */
  
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"fool": "123",
		"nbf":  time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
	})
  
  // 使用 SigendString 通过密钥 来生成jwt字符串
  
	tokenstring, err := token.SignedString(key)
	fmt.Println(tokenstring, err)
}
package main

import (
    "fmt"
    "time"

    "github.com/golang-jwt/jwt"
)

func main() {
    hmacSampleSecret := []byte("111")
    token := jwt.New(jwt.SigningMethodHS256)
    //通过New方法不能在创建的时候携带数据,因此可以通过给token.Claims赋值来定义数据
    token.Claims = jwt.MapClaims{
	"foo": "bar",
	"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
    }
    tokenString, err := token.SignedString(hmacSampleSecret)
    fmt.Println(tokenString, err)
}

2、除了用jwt.MapClaims,我们也可以使用自定义的结构

必须要实现这个接口

type Claims interface {
    Valid() error
}

整体代码

package main

import (
	"fmt"
	"github.com/golang-jwt/jwt"
)

type CustomerClaims struct {
	Username string `json:"username"`
	Gender   string `json:"gender"`
	Avatar   string `json:"avatar"`
	Email    string `json:"email"`
}

func (c CustomerClaims) Valid() error {
	return nil
}

func main() {
	//密钥
	hmacSampleSecret := []byte("111")
	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims = CustomerClaims{
		Username: "小明",
		Gender:   "男",
		Avatar:   "https://1.jpg",
		Email:    "test@163.com",
	}
	tokenString, err := token.SignedString(hmacSampleSecret)
	fmt.Println(tokenString, err)
}

如果我们想在自定义结构中使用JWT标准中定义的字段,可以这样子:

go复制代码type CustomerClaims struct {
    *jwt.StandardClaims//标准字段
    Username string `json:"username"`
    Gender   string `json:"gender"`
    Avatar   string `json:"avatar"`
    Email    string `json:"email"`
}

3、解析jwt

package main

import (
	"fmt"
	"github.com/golang-jwt/jwt/v4"
)

type CustomerClaims struct {
	Username string `json:"username"`
	Gender   string `json:"gender"`
	Avatar   string `json:"avatar"`
	Email    string `json:"email"`
}

func (c CustomerClaims) Valid() error {
	return nil
}

var hmacSampleSecret = []byte("111")

func main() {
	//密钥

	token := jwt.New(jwt.SigningMethodHS256)
	token.Claims = CustomerClaims{
		Username: "小明",
		Gender:   "男",
		Avatar:   "https://1.jpg",
		Email:    "test@163.com",
	}
	tokenString, err := token.SignedString(hmacSampleSecret)
	fmt.Println(tokenString, err)
	username, err, token := ParseToken(tokenString)
	fmt.Println(username, token.Method, err)

}

// 解析jwt字符串
func ParseToken(tokenstring string) (string, error, *jwt.Token) {
	token, err := jwt.ParseWithClaims(tokenstring, &CustomerClaims{}, func(t *jwt.Token) (interface{}, error) {
		return hmacSampleSecret, nil
	})
	claims := token.Claims.(*CustomerClaims)
	return claims.Username, err, token
}

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
package main

import (
	"fmt"

	"github.com/golang-jwt/jwt"
)

type CustomerClaims struct {
	Username string `json:"username"`
	Gender   string `json:"gender"`
	Avatar   string `json:"avatar"`
	Email    string `json:"email"`
	jwt.StandardClaims
}

func main() {
	var hmacSampleSecret = []byte("111")
        //前面例子生成的token
	tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWwj-aYjiIsImdlbmRlciI6IueUtyIsImF2YXRhciI6Imh0dHBzOi8vMS5qcGciLCJlbWFpbCI6InRlc3RAMTYzLmNvbSJ9.mJlWv5lblREwgnP6wWg-P75VC1FqQTs8iOdOzX6Efqk"

  // 第三个参数传入一个keyfunc函数 里面返回一个任意类型
	token, err := jwt.ParseWithClaims(tokenString, &CustomerClaims{}, func(t *jwt.Token) (interface{}, error) {
		return hmacSampleSecret, nil
	})

	if err != nil {
		fmt.Println(err)
		return
	}
  /*
  
  在这个代码片段中,`claims := token.Claims.(*CustomerClaims)` 是一行代码,它用于从 JWT(JSON Web Token) 中提取声明(claims)信息并将其赋值给变量 `claims`。

让我来解释这行代码的每一部分:

- `token.Claims`:`token` 是通过调用 `jwt.ParseWithClaims` 函数解析 JWT 后得到的 JWT 结构。JWT 包含了头部(header)、载荷(payload)和签名(signature)等部分,其中载荷部分包含声明信息。`token.Claims` 表示从 JWT 中获取声明信息的属性。

- `(*CustomerClaims)`:这部分是类型断言(type assertion)。它将 `token.Claims` 强制转换为类型为 `*CustomerClaims` 的指针。在这里,`CustomerClaims` 是一个自定义的结构体,用于表示 JWT 中的声明。通过这个类型断言,我们告诉编译器将 `token.Claims` 视为 `*CustomerClaims` 类型的数据。

- `claims := token.Claims.(*CustomerClaims)`:这是一个赋值语句,将 `token.Claims` 的内容转换为 `*CustomerClaims` 类型,并将其赋值给名为 `claims` 的变量。现在,`claims` 包含了从 JWT 中提取的声明信息,你可以在后续代码中使用它来访问这些声明的值。


  */
	claims := token.Claims.(*CustomerClaims)
	fmt.Println(claims)
}


**keyfunc函数示例 **

package main

import (
    "errors"
    "fmt"
    "github.com/golang-jwt/jwt"
)

func main() {
    // 示例JWT字符串
    tokenString := "..." // 请将您的JWT字符串放在这里

    // 定义一个Keyfunc函数,用于提供用于验证JWT签名的密钥
    keyFunc := func(token *jwt.Token) (interface{}, error) {
        // 如果您的JWT使用相同的密钥进行签名和验证,可以在这里返回密钥
        // 在实际应用中,密钥通常需要更复杂的管理逻辑,例如从配置文件或密钥存储中获取
        return []byte("your-secret-key"), nil
    }

    // 调用ParseWithClaims函数来解析JWT
    token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, keyFunc)
    if err != nil {
        fmt.Println("JWT parsing failed:", err)
        return
    }

    // 验证JWT是否有效
    if !token.Valid {
        fmt.Println("JWT is not valid")
        return
    }

    // JWT验证通过,您可以访问JWT中的声明信息
    claims := token.Claims.(*jwt.StandardClaims)
    fmt.Println("JWT Valid")
    fmt.Println("Subject:", claims.Subject)
    fmt.Println("Expiration Time:", claims.ExpiresAt)
    // 其他声明字段...
}

上述示例中,我们定义了一个简单的 Keyfunc 函数,它只是返回一个硬编码的密钥 []byte("your-secret-key") 用于验证JWT签名。在实际应用中,您应该根据安全最佳实践从更安全的存储位置获取密钥。

然后,我们调用 jwt.ParseWithClaims 函数来解析JWT,并将密钥提供给它。最后,我们验证JWT是否有效,并访问其中的声明信息。

4、gin-jwt实现

package main

import (
	"log"
	"net/http"
	"time"

	"github.com/appleboy/gin-jwt/v2"
	"github.com/gin-gonic/gin"
)

// 用于接受登录的用户名与密码
type login struct {
 /*
 
1. `Username` 字段:表示用户名,它具有以下标签:
   - `form:"username"`:这个标签表示这个字段可以从表单数据中获取,其中 "username" 是表单字段的名称。
   - `json:"username"`:这个标签表示这个字段可以从 JSON 数据中获取,其中 "username" 是 JSON 属性的名称。
   - `binding:"required"`:这个标签表示这个字段是必需的,也就是说,在处理登录请求时,用户名必须提供。

2. `Password` 字段:表示密码,它也有类似的标签:
   - `form:"password"`:这个标签表示这个字段可以从表单数据中获取,其中 "password" 是表单字段的名称。
   - `json:"password"`:这个标签表示这个字段可以从 JSON 数据中获取,其中 "password" 是 JSON 属性的名称。
   - `binding:"required"`:同样,这个标签表示这个字段也是必需的,密码必须提供。
*/
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

var identityKey = "id"

// jwt中payload的数据
type User struct {
	UserName  string
	FirstName string
	LastName  string
}

func main() {

	// 定义一个Gin的中间件
	authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
		Realm:            "test zone",          //标识
		SigningAlgorithm: "HS256",              //加密算法
		Key:              []byte("secret key"), //密钥
		Timeout:          time.Hour,
		MaxRefresh:       time.Hour,   //刷新最大延长时间
		IdentityKey:      identityKey, //指定cookie的id
		PayloadFunc: func(data interface{}) jwt.MapClaims { //负载,这里可以定义返回jwt中的payload数据
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					identityKey: v.UserName,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return &User{
				UserName: claims[identityKey].(string),
			}
		},
		Authenticator: Authenticator, //在这里可以写我们的登录验证逻辑
		Authorizator: func(data interface{}, c *gin.Context) bool { //当用户通过token请求受限接口时,会经过这段逻辑
			if v, ok := data.(*User); ok && v.UserName == "admin" {
				return true
			}

			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) { //错误时响应
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		// 指定从哪里获取token 其格式为:"<source>:<name>" 如有多个,用逗号隔开
		TokenLookup:   "header: Authorization, query: token, cookie: jwt",
		TokenHeadName: "Bearer",
		TimeFunc:      time.Now,
	})

	if err != nil {
		log.Fatal("JWT Error:" + err.Error())
	}
	r := gin.Default()
	//登录接口
	r.POST("/login", authMiddleware.LoginHandler)
	auth := r.Group("/auth")
	//退出登录
	auth.POST("/logout", authMiddleware.LogoutHandler)
	// 刷新token,延长token的有效期
	auth.POST("/refresh_token", authMiddleware.RefreshHandler)
	auth.Use(authMiddleware.MiddlewareFunc()) //应用中间件

	//使用 {}(大括号)来创建一个代码块
	//是为了将一组相关的路由规则组织在一起
	//形成一个路由组。这是 Gin 框架的常见做法
	{
		auth.GET("/hello", helloHandler)
	}

	if err := http.ListenAndServe(":8005", r); err != nil {
		log.Fatal(err)
	}
}

func Authenticator(c *gin.Context) (interface{}, error) {
	var loginVals login
	if err := c.ShouldBind(&loginVals); err != nil {
		return "", jwt.ErrMissingLoginValues
	}
	userID := loginVals.Username
	password := loginVals.Password

	if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
		return &User{
			UserName:  userID,
			LastName:  "Bo-Yi",
			FirstName: "Wu",
		}, nil
	}

	return nil, jwt.ErrFailedAuthentication
}

// 处理/hellow路由的控制器
func helloHandler(c *gin.Context) {
	claims := jwt.ExtractClaims(c)
	user, _ := c.Get(identityKey)
	c.JSON(200, gin.H{
		"userID":   claims[identityKey],
		"userName": user.(*User).UserName,
		"text":     "Hello World.",
	})
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌微阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值