go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证

什么是JWT

JWT,全名为JSON Web Token,是当下主流的一种服务端通信认证方式,具有轻量,无状态的特点,它实现了让我们在用户与服务器之间传递安全可靠的Json文本信息,它的使用过程主要是这样的:
当用户注册的时候,服务端会接受到来自用户输入的账号与密码,然后服务端会向客户端发送JWT,而当客户端有了JWT这个令牌后,当下一次客户端服务端请求数据时,我们只要利用这个令牌,就可以轻松访问服务端的数据了,因此这种信息传输方式也有着开销少传输安全的特点。

JWT的下载

终端输入以下命令即可:

go get -u github.com/golang-jwt/jwt/v4

JWT的构成

简介

RFC标准中,JWT由以下三个部分组成:

  • Header: 头部
  • Payload: 载荷
  • Signature: 签名

我们会将这里的每一个部分用一个点.来分隔,最后组成一个字符串,格式如下:

header.payload.signature

而这就是一个JWT令牌的标准结构,接下来网格大家来逐个讲解每个结构的作用。

头部

Header中主要是声明一些基本信息,通常是由两部分来组成:

  • 令牌的类型
  • 签名所使用的加密算法
    比如下面的这个示例:
{
	"alg":"HS526",
	"typ":"JWT"
}

上面这个Json格式的数据意思大致为:令牌的类型为JWT,签名所使用的加密算法为HS526,最后再将JSON对象通过Base64Url编码为字符串,该字符串就是JWT的头部

载荷

JWT的第二部分是载荷部分,主要就是声明部分claims,声明部分通常是一个实体的数据,比如一个用户。而关于声明的类型主要有以下几种:

  • reigsteredReigetered claims代表着一些预定义的声明 ,例如:iss(issuer 签发者),exp(expiration time 过期时间),aud(audience 受众)
  • public:Puiblic claims这部分可以让使用JWT的人随意定义,但是最好避免与其他声明部分冲突
  • private claims:这部分的声明同样也是自定义的,通常用于在服务双方共享一些信息。

示例:

{
	"sub": "1234567890",
  	"name": "John Doe",
  	"admin": true
}

该JSON对象将通过Base64Url编码为字符串,该字符串就是JWT的第二部分。

注意:虽然载荷部分也受到保护,也有防篡改,但是这一部分是公共可读的,所以不要把敏感信息存放在JWT内。

签名

在获得了编码的头部和编码的载荷部分后,就可以通过头部所指明的签名算法根据前两个部分的内容再加上密钥进行加密签名,所以一旦JWT的内容有任何变化,解密时得到的签名都会不一样,同时如果是使用私钥,也可以对JWT的签发者进行验证。

JWT的工作原理

在身份验证中,用户使用凭据成功登录是,将会返回一个JSON WEB令牌,由于令牌是凭证,所以要求我们要常常小心地防止出现安全问题,所以令牌的保存时间不应超过其所需的时间,同时无论何时用户想要访问受保护的路由与资源,在发送请求时都必须携带上token,服务端在收到JWT后会对其进行有效性认证,例如内容有篡改,token已过期等等,如果验证通过就可以顺利的访问资源。

注意:虽然JWT允许我们携带一些基本的信息,但是建议不要带有过大信息量的数据。

JWT的使用案例

这里我主要会以一个通过JWT来实现的登录验证中间件来讲解一下我们如何在项目中使用JWT:

设置JWTKey

首先我们在config文件中设置JWTKey:
在这里插入图片描述

然后我们基于这个配置文件读取来读取配置:

package utils

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"gopkg.in/ini.v1"
)

type Config struct {
	Server   *server   `ini:"server"`
	Database *database `ini:"database"`
}

type server struct {
	AppMode  string `ini:"AppMode"`
	HttpPort string `ini:"HttpPort"`
	JWTKey   string `ini:"JWTKey"`
}

type database struct {
	Db         string `ini:"Db"`
	DbName     string `ini:"DbName"`
	DbUser     string `ini:"DbUser"`
	DbPassWord string `ini:"DbPassWord"`
	DbHost     string `ini:"DbHost"`
	DbPort     string `ini:"DbPort"`
}

var ServerSetting = &server{
	AppMode:  "debug",
	HttpPort: ":3000",
	JWTKey:   "FengXu123",
}

var DatabaseSetting = &database{
	Db:         "mysql",
	DbName:     "goblog",
	DbUser:     "root",
	DbPassWord: "ba161754",
	DbHost:     "localhost",
	DbPort:     "3306",
}

// Config_Message
var Config_Message = &Config{
	Server:   ServerSetting,
	Database: DatabaseSetting,
}

func init() {
	filename := "config/config.ini"
	cfg, err := ini.Load(filename)
	if err != nil {
		logrus.Errorf("配置文件加载失败: %v", err)
	}
	err = cfg.MapTo(Config_Message)
	if err != nil {
		logrus.Errorf("配置文件映射失败: %v", err)
	}
	fmt.Println(Config_Message.Server.JWTKey)
	logrus.Infof("配置文件加载成功")
}

具体go-ini第三方包的使用可以参考博主以前的文章:
go语言并发实战——日志收集系统(五) 基于go-ini包读取日志收集服务的配置文件

运行程序后,控制台显示:
在这里插入图片描述

说明我们的配置信息就成功被加载出来了。

定义相关结构体与信息

// JWT结构体
type JWT struct {
	JWTKey []byte // JWT密钥
}

func NewJWT() *JWT { //新建JWT结构体
	return &JWT{
		JWTKey: []byte(utils.Config_Message.Server.JWTKey),
	}
}

// 自定义声明
type MyClaims struct {
	Username string `json:"username"` //这里的与gorm中声明的保持一致
	jwt.RegisteredClaims
}

// 定义相关错误信息
var (
	TokenExpired     error = errors.New("token已过期,请重新登录")
	TokenNotValidYet error = errors.New("token无效,请重新登录")
	TokenMalformed   error = errors.New("token不正确,请重新登录")
	TokenInvalid     error = errors.New("这不是一个token,请重新登录")
)

生成token

func(j *JWT)CreateToken(claims MyClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.JWTKey)
}

部分函数记录

  1. NewWithClaims:
func NewWithClaims(method SigningMethod, claims Claims) *Token
  • 作用:创建一个新的token

  • 相关参数:

    • SigningMethod:所采用的加密方法
    • claims:我们所定义的声明
  1. SignedString
  • 作用:SignedString创建并返回一个完整的、已签名的JWT

解析token

// 解析token

func (j *JWT) ParseToken(tokenString string) error {
	// 解析token
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		return j.JWTKey, nil
	})

	// 校验token
	if token.Valid {
		return nil
	} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
		return TokenExpired
	} else if errors.Is(err, jwt.ErrSignatureInvalid) {
		return TokenInvalid
	} else if errors.Is(err, jwt.ErrTokenMalformed) {
		return TokenMalformed
	} else {
		return TokenNotValidYet
	}
}

实现token中间件

//jwt中间件

func JwtToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		var code int
		//检验Header
		tokenHeader := c.Request.Header.Get("Authorization")
		if tokenHeader == "" {
			code = errmsg.ERROR_TOKEN_NOT_EXIST
			c.JSON(200, gin.H{
				"status":  code,
				"message": errmsg.GetErrMsg(code),
			})
			c.Abort() //拦截中间件
		}
		checkToken := strings.Split(tokenHeader, " ")
		if len(checkToken) == 0 {
			code = errmsg.ERROR_TOKEN_TYPE_WRONG
			c.JSON(200, gin.H{
				"status":  code,
				"message": errmsg.GetErrMsg(code),
			})
			c.Abort()
		}

		if len(checkToken) != 2 || checkToken[0] != "Bearer" {
			c.JSON(200, gin.H{
				"status":  code,
				"message": errmsg.GetErrMsg(code),
			})
			c.Abort()
		}

		//解析token
		j := NewJWT()
		err := j.ParseToken(checkToken[1])
		if err != nil {
			if errors.Is(err, TokenExpired) {
				c.JSON(200, gin.H{
					"status":  errmsg.ERROR_TOKEN_RUNTIME,
					"message": errmsg.GetErrMsg(errmsg.ERROR_TOKEN_RUNTIME),
				})
				c.Abort()
			} else {
				c.JSON(200, gin.H{
					"status":  errmsg.ERROR,
					"message": err.Error(),
				})
				c.Abort()
			}
		}
		c.Next()
	}
}

完整代码

package middleware

import (
	"errors"
	"gin_vue_blog/utils"
	"gin_vue_blog/utils/errmsg"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v4"
	"strings"
)

// JWT结构体
type JWT struct {
	JWTKey []byte // JWT密钥
}

func NewJWT() *JWT { //新建JWT结构体
	return &JWT{
		JWTKey: []byte(utils.Config_Message.Server.JWTKey),
	}
}

// 自定义声明
type MyClaims struct {
	Username string `json:"username"` //这里的与gorm中声明的保持一致
	jwt.RegisteredClaims
}

// 定义相关错误信息
var (
	TokenExpired     error = errors.New("token已过期,请重新登录")
	TokenNotValidYet error = errors.New("token无效,请重新登录")
	TokenMalformed   error = errors.New("token不正确,请重新登录")
	TokenInvalid     error = errors.New("这不是一个token,请重新登录")
)

// 生成token
func (j *JWT) CreateToken(claims MyClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.JWTKey)
}

// 解析token

func (j *JWT) ParseToken(tokenString string) error {
	// 解析token
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		return j.JWTKey, nil
	})

	// 校验token
	if token.Valid {
		return nil
	} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
		return TokenExpired
	} else if errors.Is(err, jwt.ErrSignatureInvalid) {
		return TokenInvalid
	} else if errors.Is(err, jwt.ErrTokenMalformed) {
		return TokenMalformed
	} else {
		return TokenNotValidYet
	}
}

//jwt中间件

func JwtToken() gin.HandlerFunc {
	return func(c *gin.Context) {
		var code int
		//检验Header
		tokenHeader := c.Request.Header.Get("Authorization")
		if tokenHeader == "" {
			code = errmsg.ERROR_TOKEN_NOT_EXIST
			c.JSON(200, gin.H{
				"status":  code,
				"message": errmsg.GetErrMsg(code),
			})
			c.Abort() //拦截中间件
		}
		checkToken := strings.Split(tokenHeader, " ")
		if len(checkToken) == 0 {
			code = errmsg.ERROR_TOKEN_TYPE_WRONG
			c.JSON(200, gin.H{
				"status":  code,
				"message": errmsg.GetErrMsg(code),
			})
			c.Abort()
		}

		if len(checkToken) != 2 || checkToken[0] != "Bearer" {
			c.JSON(200, gin.H{
				"status":  code,
				"message": errmsg.GetErrMsg(code),
			})
			c.Abort()
		}

		//解析token
		j := NewJWT()
		err := j.ParseToken(checkToken[1])
		if err != nil {
			if errors.Is(err, TokenExpired) {
				c.JSON(200, gin.H{
					"status":  errmsg.ERROR_TOKEN_RUNTIME,
					"message": errmsg.GetErrMsg(errmsg.ERROR_TOKEN_RUNTIME),
				})
				c.Abort()
			} else {
				c.JSON(200, gin.H{
					"status":  errmsg.ERROR,
					"message": err.Error(),
				})
				c.Abort()
			}
		}
		c.Next()
	}
}

备注:拦截中间件与响应中间件的具体细节可以参考博主之前的文章:
Gin框架学习笔记(五) ——文件上传与路由中间件

拓展:签名算法的选择(仅供参考)

可用的签名算法有好几种,在使用之前应该先了解下它们之间的区别以便更好的去选择签名算法,它们之间最大的不同就是对称加密和非对称加密。

最简单的对称加密算法HSA,让任何[]byte都可以用作有效的密钥,所以计算速度稍微快一点。在生产者和消费者双方都是可以被信任的时候,对称加密算法的效率是最高的。不过由于签名和验证都使用相同的密钥,因此无法轻松的分发用于验证的密钥,毕竟签名的密钥也是同一个,签名泄露了则JWT的安全性就毫无意义。

非对称加密签名方法,例如RSA,使用不同的密钥来进行签名和验证token,这使得生成带有私钥的令牌成为可能,同时也允许任何使用公钥验证的人正常访问。

不同的签名算法所需要的密钥的类型也不同,下面给出一些常见签名算法的类型:

  • HMAC:对称加密,需要类型[]byte的值用于签名和验证。 (HS256,HS384,HS512)
  • RSA:非对称加密,需要rsa.PrivateKey类型的值用于签名,和rsa.PublicKey类型的值用于验证。(RS256,RS384,RS512)
  • ECDSA:非对称加密,需要ecdsa.PrivateKey类型的值用于签名,和ecdsa.PublicKey类型的值用于验证。(ES256,ES384,ES512)
  • EdDSA:非对称加密,需要ed25519.PrivateKey类型的值用于签名和ed25519.PublicKey 类型的值用于验证。(Ed25519)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/693690.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux——nginx部署

部署Nginx 构建Nginx服务器 (实验需要DNS支持,或添加hosts条目,例如: ) 安装Nginx(yum安装即可) 安装依赖软件包: 重启、启用服务并查看服务状态: 默认页面&#xff0…

【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境

【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境 大家好 我是寸铁👊 总结了一篇【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境✨ 喜欢的小伙伴可以点点关注 &#…

模糊控制器实现对某个对象追踪输入

MATLAB是一个十分便捷的软件,里面提供了许多集成的组件,本文利用simulink实现模糊控制器实现对某个对象追踪输入。 这里的对象根据自己的需求可以修改,那么搭建一个闭环控制系统并不是难事儿,主要是对于模糊控制器参数的设置&…

海思SD3403,SS928/926,hi3519dv500,hi3516dv500移植yolov7,yolov8(21)Yolov9s测试

四天前yolov9的作者终于开源了yolov9s和yolov9t模型。这个作者之前一直没开源t,s,只有c开始的,而且onnx转换后数据大小特别大,当时直接就放弃测试了。 另外之前代码有很明显的抄v5的痕迹。所以印象很不好。 现在总算是开源t,s模型,而且这里评估的结果上来看是好于yolov8的…

IDEA创建Mybatis项目

IDEA创建Mybatis项目 第一步:创建库表 -- 创建数据库 create database mybatis_db;-- 使用数据库 use mybatis_db;-- 创建user表 CREATE TABLE user (id INT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) NOT NULL,password VARCHAR(50) NOT NULL,email VARC…

Django API开发实战:前后端分离、Restful风格与DRF序列化器详解

系列文章目录 Django入门全攻略:从零搭建你的第一个Web项目Django ORM入门指南:从概念到实践,掌握模型创建、迁移与视图操作Django ORM实战:模型字段与元选项配置,以及链式过滤与QF查询详解Django ORM深度游&#xff…

项目-五子棋双人对战:游戏房间的管理(5)

完整代码见: 邹锦辉个人所有代码: 测试仓库 - Gitee.com 之前我们已经实现了玩家匹配的功能, 我们都知道, 匹配完过后就可以进入游戏房间进行对战了, 所以我们下一步关注的重点就是对于游戏房间的管理. 模块详细讲解 功能需求 通过匹配的方式, 自动给玩家加入到一个游戏房间…

通过fiftyone按分类下载open-images-v7数据集,并转成yolov5可直接训练的格式

import osimport fiftyone as fo import fiftyone.zoo as foz import yamlclasses [Person, # 人 - 0Car, # 轿车 - 1Taxi, # 出租车 - 2Ambulance, # 救护车 - 3Bus, # 公共汽车 - 4Bicycle, # 自行车 - 5Motorcycle, # 摩托车 - 6Dog, # 狗 - 7Cat, # 猫 - 8M…

配置免密登录秘钥报错

移除秘钥,执行 ssh-keygen -R cdh2即可 参考:ECDSA主机密钥已更改,您已请求严格检查。 - 简书

构建第一个ArkTS应用之@卡片事件能力说明

ArkTS卡片中提供了postCardAction()接口用于卡片内部和提供方应用间的交互,当前支持router、message和call三种类型的事件,仅在卡片中可以调用。 接口定义:postCardAction(component: Object, action: Object): void 接口参数说明&#xff1…

Hadoop笔记

1.hadoop环境搭建,linux命令(vi);2.分布式的基本概念,cap理论(遵循此原则开发分布式数据库),hdfs,mapreduce;3.3.1;3.2重点;4.map,reduce过程,优缺…

OrangePi Kunpeng Pro深度评测:性能与体验的完美融合

文章目录 一、引言二、硬件开箱与介绍1.硬件清单2.硬件介绍 三、软件介绍四、性能测试1. 功率测试2. cpu测试2.1 单线程cpu测试2.2 多线程cpu测试 五、实际开发体验1. 搭建API服务器2. ONNX推理测试3. 在线推理平台 五、测评总结1. 能与硬件配置2. 系统与软件3. 实际开发体验个…

大模型的演进之路:从萌芽到ChatGPT的辉煌

文章目录 ChatGPT:大模型进化史与未来展望引言:大模型的黎明统计模型的奠基深度学习的破晓 GPT系列:预训练革命GPT的诞生:预训练微调的范式转换GPT-2:规模与能力的双重飞跃GPT-3:千亿美元参数的奇迹 ChatGP…

基于Python的AI动物识别技术研究

基于Python的AI动物识别技术研究 开发语言:Python 数据库:MySQL所用到的知识:Django框架工具:pycharm、Navicat、Maven 系统功能实现 系统的登录模块设计 本次设计的AI动物识别系统为了保证用户的数据安全,设计了登录的模块&…

【乐吾乐2D可视化组态编辑器】在线使用,快速入门

一、在线使用 乐吾乐2D可视化组态编辑器地址:https://2d.le5le.com/ 二、步骤 本教程将带领你快速体验2D可视化编辑器的全流程开发。 1.创建图纸 进入2d编辑器主界面后,主界面最中心为图纸面板,默认为空图纸,在界面左侧为组…

算法导论实战(六)(算法导论习题三十四、三十五章)

🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀算法启示录 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 前言 算法导论的知识点学习将持续性更新在算…

win设置ftp服务器~java通过ftp下载文件

1.先设置ftp 2.打开服务 3.设置站点 4.起名字 这样就可以了 5.剩下的就是设置权限和账号了,找到对应的按钮就可以了 6.下载文件的代码 public byte[] downloadFile(File file) throws IOException{ByteArrayOutputStream out new ByteArrayOutputStream();toDi…

把chatgpt当实习生,进行matlab gui程序编程

最近朋友有个项目需要整点matlab代码,无奈自己对matlab这种工科的软件完全是外行,无奈只有求助gpt这种AI助手了。大神们告诉我们,chatgpt等的助手已经是大学实习生水平啦,通过多轮指令交互就可以让他帮你完成工作啦!所…

使用 Scapy 库编写 TCP RST 攻击脚本

一、介绍 TCP RST攻击是一种拒绝服务攻击(Denial-of-Service, DoS)类型,攻击者通过伪造TCP重置(RST)包,中断目标主机与其他主机之间的TCP连接。该攻击利用了TCP协议中的重置机制,强制关闭合法的…

倩女幽魂手游攻略:云手机自动搬砖辅助教程!

《倩女幽魂》手游自问世以来一直备受玩家喜爱,其精美画面和丰富的游戏内容让人沉迷其中。而如今,借助VMOS云手机,玩家可以更轻松地进行搬砖,提升游戏体验。 一、准备工作 下载VMOS云手机: 在PC端或移动端下载并安装VM…