gin框架中使用JWT的定义需求及解析

 更新时间:2022年04月16日 13:27:31   作者:Jeff的技术栈  
这篇文章主要为介绍了gin框架中使用JWT的定义需求及解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪

什么是JWT?

JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth3.0业务场景下。

为什么需要JWT?

在之前的一些web项目中,我们通常使用的是Cookie-Session模式实现用户认证。相关流程大致如下:

  • 用户在浏览器端填写用户名和密码,并发送给服务端
  • 服务端对用户名和密码校验通过后会生成一份保存当前用户相关信息的session数据和一个与之对应的标识(通常称为session_id)
  • 服务端返回响应时将上一步的session_id写入用户浏览器的Cookie
  • 后续用户来自该浏览器的每次请求都会自动携带包含session_id的Cookie
  • 服务端通过请求中的session_id就能找到之前保存的该用户那份session数据,从而获取该用户的相关信息。

这种方案依赖于客户端(浏览器)保存Cookie,并且需要在服务端存储用户的session数据。

在移动互联网时代,我们的用户可能使用浏览器也可能使用APP来访问我们的服务,我们的web应用可能是前后端分开部署在不同的端口,有时候我们还需要支持第三方登录,这下Cookie-Session的模式就有些力不从心了。

JWT就是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。

想要连接JWT的原理,推荐大家阅读:JWT入门教程

生成JWT和解析JWT

我们在这里直接使用jwt-go这个库来实现我们生成JWT和解析JWT的功能。

定义需求

我们需要定制自己的需求来决定JWT中保存哪些数据,比如我们规定在JWT中要存储username信息,那么我们就定义一个MyClaims结构体如下:

import (
	"github.com/dgrijalva/jwt-go"
)
// MyClaims 自定义声明结构体并内嵌jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个username字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type MyClaims struct {
	Username string `json:"username"`
	jwt.StandardClaims
}

然后我们定义JWT的过期时间,这里以2小时为例:

const TokenExpireDuration = time.Hour * 2

接下来还需要定义Secret:

var MySecret = []byte("夏天夏天悄悄过去")

生成JWT

// GenToken 生成JWT
func GenToken(username string) (string, error) {
	// 创建一个我们自己的声明
	c := MyClaims{
		"username", // 自定义字段
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer:    "my-project",                               // 签发人
		},
	}
	// 使用指定的签名方法创建签名对象
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return token.SignedString(MySecret)
}

解析JWT

// ParseToken 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
	// 解析token
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		return MySecret, nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // 校验token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

在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 == "q1mi" && user.Password == "q1mi123" {
		// 生成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")来获取当前请求的用户信息
	}
}

注册一个/home路由,发个请求验证一下吧。

r.GET("/home", JWTAuthMiddleware(), homeHandler)
func homeHandler(c *gin.Context) {
	username := c.MustGet("username").(string)
	c.JSON(http.StatusOK, gin.H{
		"code": 2000,
		"msg":  "success",
		"data": gin.H{"username": username},
	})
}

如果不想自己实现上述功能,你也可以使用Github上别人封装好的包。

以上就是gin框架中使用JWT的定义需求及解析的详细内容,更多关于gin框架中使用JWT的资料请关注脚本之家其它相关文章!

相关文章

  • golang实现整型和字节数组之间的转换操作

    golang实现整型和字节数组之间的转换操作

    这篇文章主要介绍了golang实现整型和字节数组之间的转换操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go语言tunny的workerWrapper使用教程示例

    Go语言tunny的workerWrapper使用教程示例

    这篇文章主要为大家介绍了Go语言tunny的workerWrapper使用教程示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • 一文带你了解Golang中的缓冲区Buffer

    一文带你了解Golang中的缓冲区Buffer

    作为一种常见的数据结构,缓冲区(Buffer)在计算机科学中有着广泛的应用。这篇文章将详细介绍 Go 中 Buffer 的用法,从多个方面介绍其特性和应用场景,需要的可以参考一下
    2023-05-05
  • golang elasticsearch Client的使用详解

    golang elasticsearch Client的使用详解

    这篇文章主要介绍了golang elasticsearch Client的使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-05-05
  • GO使用socket和channel实现简单控制台聊天室

    GO使用socket和channel实现简单控制台聊天室

    今天小编给大家分享一个简单的聊天室功能,聊天室主要功能是用户可以加入离开聊天室,实现思路也很简单明了,下面小编给大家带来了完整代码,感兴趣的朋友跟随小编一起看看吧
    2021-12-12
  • Golang实现Java虚拟机之解析class文件详解

    Golang实现Java虚拟机之解析class文件详解

    这篇文章主要为大家详细介绍了Golang实现Java虚拟机之解析class文件的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-01-01
  • golang正则之命名分组方式

    golang正则之命名分组方式

    这篇文章主要介绍了golang正则之命名分组方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • go语言操作redis连接池的方法

    go语言操作redis连接池的方法

    这篇文章主要介绍了go语言操作redis连接池的方法,涉及Go语言操作radis的技巧,需要的朋友可以参考下
    2015-03-03
  • Go语言实现机器大小端判断代码分享

    Go语言实现机器大小端判断代码分享

    这篇文章主要介绍了Go语言实现机器大小端判断代码分享,本文直接给出实现代码,需要的朋友可以参考下
    2014-10-10
  • go mod tidy拉取依赖包bug问题及解决

    go mod tidy拉取依赖包bug问题及解决

    这篇文章主要介绍了go mod tidy拉取依赖包bug问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09

最新评论