GoWeb 进阶的实战项目,基于 Iris 框架实现 JWT 认证(附案例全代码)

news2024/11/24 22:34:30

1、前言

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

2、什么是 JWT

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT 规定了数据传输的结构,一串完整的 JWT 由三段落组成,每个段落用英文句号连接 . 连接,他们分别是标头(Header)、有效负荷(Payload)和签名(Signature)三个部分,所以常规的 JWT 内容格式是这样的:aaa.bbb.ccc,即:Header.Payload.Signature

在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token。在某些情况下,这可以是无状态授权机制。服务器的受保护路由将在Authorization Header中检查有效的JWT,如果存在,则允许用户访问 受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。需要注意的是,使用签名Token,Token中包含的所有信息都会向用户或其他方公开,即使他们无法更改。这意味着您不应该在Token中放置秘密信息。

3、什么是 UUID

UUID是国际标准化组织(ISO)提出的一个概念。UUID是一个128比特的数值,这个数值可以通过一定的算法计算出来。为了提高效率,常用的UUID可缩短至16位。UUID用来识别属性类型,在所有空间和时间上被视为唯一的标识。一般来说,可以保证这个值是真正唯一的任何地方产生的任意一个UUID都不会有相同的值。使用UUID的一个好处是可以为新的服务创建新的标识符。这样一来,客户端在查找一个服务时,只需要在它的服务查找请求中指出与某类服务(或某个特定服务)有关的UUID,如果服务的提供者能将可用的服务与这个UUID相匹配,就返回一个响应。

UUID是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。UUID可以被任何人独立创建,并按需发布。UUID没有集中管理机构,因为它们是不会被复制的独特标识符。属性协议允许设备使用UUID识别属性类型,从而不需要用读/写请求来识别它们的本地句柄。

4、编写代码

此项目根据 《GoWeb 的 MVC 入门实战案例,基于 Iris 框架实现(附案例全代码)》源代码进阶,如果只想了解 JWT 认证可以忽略

4.1、安装软件包

# 1、下载并安装 jwt
go get -u github.com/golang-jwt/jwt/v4

# 2、下载并安装 uuid
go get -u github.com/gofrs/uuid

4.2、编写工具包代码

工欲善其事,必先利其器,我们从工具包开始

4.2.1、日期时间工具类

在 utils/tool 目录新建文件 datatime_utils.go,编写如下代码

package tool

import "time"

/**
 * <h1>日期时间工具类</h1>
 * Created by woniu
 * second,秒
 * millisecond,毫秒
 * microsecond,微妙
 * nanosecond,纳秒
 */

/**
 * <h2>获取当前时间</h2>
 */
func DataTimeNow() time.Time {
	return time.Now()
}

/**
 * <h2>获取指定时间 - 秒</h2>
 */
func DataTimeUnix(t time.Time) int64 {
	return t.Unix()
}

/**
 * <h2>获取指定时间 - 秒</h2>
 */
func DataTimeNowUnix() int64 {
	return DataTimeUnix(DataTimeNow())
}

/**
 * <h2>获取指定时间 - 毫秒</h2>
 */
func DataTimeUnixMilli(t time.Time) int64 {
	return t.UnixMilli()
}

/**
 * <h2>获取当前时间 - 毫秒</h2>
 */
func DataTimeNowUnixMilli() int64 {
	return DataTimeUnixMilli(DataTimeNow())
}

/**
 * <h2>指定日期增加几个小时</h2>
 */
func DataTimeAddHour(t time.Time, h time.Duration) time.Time {
	return t.Add(h * time.Hour)
}

/**
 * <h2>格式化时间,yyyy-mm-dd hh:mm:ss</h2>
 */
func DataTimeFormat(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}

/**
 * <h2>格式化时间,yyyy-mm-dd</h2>
 */
func DataFormat(t time.Time) string {
	return t.Format("2006-01-02")
}

/**
 * <h2>格式化时间,hh:mm:ss</h2>
 */
func TimeFormat(t time.Time) string {
	return t.Format("15:04:05")
}

4.2.2、字符串工具类

在 utils/tool 目录新建文件 string_utils.go,编写如下代码

package tool

import (
	"fmt"
	"strconv"
	"time"

	"github.com/gofrs/uuid"
)

/**
 * <h1>字符串工具类</h1>
 * Created by woniu
 */

/***
 * <h2>空校验</h2>
 */
func IsEmpty(str string) bool {
	return len(str) == 0
}

/***
 * <h2>非空校验</h2>
 */
func IsNotEmpty(str string) bool {
	return !IsEmpty(str)
}

/***
 * <h2>字符串在数组内</h2>
 */
func IsStringInArray(value string, array []string) bool {
	for _, v := range array {
		if v == value {
			return true
		}
	}
	return false
}

/***
 * <h2>生成 UnixNano 字符串</h2>
 */
func GetUnixNano() string {
	return strconv.FormatInt(time.Now().UnixNano(), 10)
}

/***
 * <h2>生成UUID</h2>
 */
func GetUUID() string {
	return GetUUIDV4()
}

/***
 * <h2>生成UUID</h2>
 * 基于mac地址、时间戳
 */
func GetUUIDV1() string {
	// Version 1: 时间 + Mac地址
	id, err := uuid.NewV1()
	if err != nil {
		fmt.Printf("uuid NewUUID err:%+v", err)
	}
	return id.String()
}

/***
 * <h2>生成UUID</h2>
 * 纯随机数
 */
func GetUUIDV4() string {
	id, err := uuid.NewV4()
	if err != nil {
		fmt.Printf("uuid NewUUID err:%+v", err)
	}
	return id.String()
}

4.2.3、JWT 工具类

注意:此工具类是本次项目进阶核心代码

在 utils/tool 目录新建文件 jwt_utils.go,编写如下代码

package tool

import (
	"fmt"
	"go-iris/app/vo"

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

/**
 * <h1>JWT 工具类</h1>
 * Created by woniu
 */

// JWT 秘钥,自己自定义
var jwtSecret = []byte("csdn_vip")

// Claim 是一些实体(通常指的用户)的状态和额外的元数据
type DClaims struct {
	UserId   int    `json:"userId"`   // 用户ID
	UserName string `json:"userName"` // 用户姓名
	Account  string `json:"account"`  // 用户账号
	jwt.StandardClaims
}

/*
* <h2>打印当前结构的信息</h2>
**/
func (to *DClaims) ToString() {
	fmt.Println("用户 ID:", to.UserId)
	fmt.Println("用户姓名:", to.UserName)
	fmt.Println("用户账号:", to.Account)
	fmt.Println("唯一ID:", to.StandardClaims.Id)
	fmt.Println("主题:", to.StandardClaims.Subject)
	fmt.Println("发行人:", to.StandardClaims.Issuer)
	fmt.Println("发布时间:", to.StandardClaims.IssuedAt)
	fmt.Println("生效时间:", to.StandardClaims.NotBefore)
	fmt.Println("过期时间:", to.StandardClaims.ExpiresAt)
}

/**
 * <h2>生成 JWT 的 Token</h2>
 */
func SignedJwtTokenBySysUser(sysUser *vo.SysUser) string {

	// 获取系统当前时间
	dataTime := DataTimeNow()
	// 过期时间,2 个小时
	expTime := DataTimeAddHour(dataTime, 2)

	claims := DClaims{
		UserId:   1,
		UserName: sysUser.Name,
		Account:  sysUser.Account,
		StandardClaims: jwt.StandardClaims{
			Id:        GetUUID(),              // 唯一 ID
			Subject:   "主题",                   // 主题
			Issuer:    "发行人",                  // 发行人
			IssuedAt:  DataTimeUnix(dataTime), // 发布时间,单位:秒
			NotBefore: DataTimeUnix(dataTime), // 生效时间,单位:秒
			ExpiresAt: DataTimeUnix(expTime),  // 过期时间,单位:秒

		},
	}

	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 该方法内部生成签名字符串,再用于获取完整、已签名的token
	tokenString, _ := tokenClaims.SignedString(jwtSecret)

	return tokenString
}

/**
 * <h2>解析 JWT 的 Token</h2>
 */
func ParseWithClaims(tokenString string) (*DClaims, error) {

	// 用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Token
	tokenClaims, err := jwt.ParseWithClaims(tokenString, &DClaims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})

	if err != nil {
		return nil, err
	}

	if tokenClaims != nil {
		// 从 Token 中获取到 Claims 对象,并使用断言,将该对象转换为我们自己定义的 DClaims
		// 要传入指针,项目中结构体都是用指针传递,节省空间。
		if claims, ok := tokenClaims.Claims.(*DClaims); ok && tokenClaims.Valid {
			return claims, nil
		}
	}
	return nil, err
}

4.2.4、用户登录功能

4.2.4.1、系统用户响应结构

在 app/vo 目录修改文件 sys_user_vo.go,完整代码如下:

package vo

// 系统用户信息
type SysUser struct {
	Account string `json:"account"` // 用户账号
	Name    string `json:"name"`    // 用户姓名
	Age     int    `json:"age"`     // 用户年龄
	Token   string `json:"token"`   // token
}

4.2.4.2、系统用户业务处理

在 app/controller 目录修改文件 sys_user_controller.go,完整代码如下:

package controller

import (
	"go-iris/app/dto"
	"go-iris/app/service"
	"go-iris/utils/common"
	"go-iris/utils/tool"

	"github.com/kataras/iris/v12"
)

/**
 * <h1>系统用户控制器</h1>
 * Created by woniu
 */
var SysUser = new(SysUserController)

type SysUserController struct{}

/**
 * <h2>用户名密码登录</h2>
 */
func (sc *SysUserController) PasswordLogin(ctx iris.Context) {

	// 登录参数
	var requestModel dto.LoginDto

	// 参数绑定
	if err := ctx.ReadForm(&requestModel); err != nil {
		ctx.JSON(common.ResponseError(-1, "传参异常"))
		return
	}

	// 用户登录
	sysUser, err := service.SysUser.PasswordLogin(requestModel)

	if err != nil {
		// 响应失败
		ctx.JSON(common.ResponseErrorMessage(err.Error()))
		return
	}

	// 生成 JWT 的 Token
	sysUser.Token = tool.SignedJwtTokenBySysUser(sysUser)

	// 响应成功
	ctx.JSON(common.ResponseSuccess(sysUser))
}

4.2.5、业务异常自定义

在 utils/constant 目录修改文件 errors.go,完整代码如下:

package constant

import "errors"

/**
 * <h1>业务异常自定义</h1>
 * Created by woniu
 */

var (
	ResErrAuthorizationIsNilErr = errors.New("1001_token 为空")
	ResErrSysUserPasswordErr    = errors.New("1003_密码错误")
	ResErrSysUserIsNil          = errors.New("1004_用户不存在")
)

4.2.6、请求路由

在 router 目录修改文件 router.go,完整代码如下:

package router

import (

	// 自己业务 controller 路径
	"fmt"
	"go-iris/app/controller"
	"go-iris/utils/common"
	"go-iris/utils/constant"
	"go-iris/utils/tool"

	"github.com/kataras/iris/v12"
)

/**
 * <h2>注册路由</h2>
 */
func RegisterRouter(app *iris.Application) {

	// 跨域解决方案
	app.Use(Cors)
	// 登录验证中间件
	app.Use(CheckAuthorization)

	// 系统用户
	login := app.Party("/sysUser")
	{
		// 用户名 + 密码登录
		login.Post("/passwordLogin", controller.SysUser.PasswordLogin)
	}

	// 通用异常
	err := app.Party("/error")
	{
		err.Get("/responseError", controller.Error.ResponseError)
		err.Get("/responseErrorCode", controller.Error.ResponseErrorCode)
		err.Get("/responseErrorMessage", controller.Error.ResponseErrorMessage)
	}
}

/**
 * <h2>跨域解决方案</h2>
 */
func Cors(ctx iris.Context) {
	ctx.Header("Access-Control-Allow-Origin", "*")
	if ctx.Request().Method == "OPTIONS" {
		ctx.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS")
		ctx.Header("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization")
		ctx.StatusCode(204)
		return
	}
	ctx.Next()
}

/**
 * <h2>登录验证中间件</h2>
 */
func CheckAuthorization(ctx iris.Context) {
	fmt.Println("登录验证中间件", ctx.Path())
	// 放行设置
	urlItem := []string{"/sysUser/passwordLogin", "/sysUser/login"}
	if !tool.IsStringInArray(ctx.Path(), urlItem) {
		// 从请求头中获取Token
		token := ctx.GetHeader("Authorization")

		// 请求头 Authorization 为空
		if tool.IsEmpty(token) {
			ctx.JSON(common.ResponseErrorMessage(constant.ResErrAuthorizationIsNilErr.Error()))
			return
		}

		claims, err := tool.ParseWithClaims(token)
		if err != nil {
			fmt.Println("token 解析异常信息:", err)
			ctx.JSON(common.ResponseErrorMessage(err.Error()))
			return
		}

		// 打印消息
		claims.ToString()
	}
	// 前置中间件
	ctx.Application().Logger().Infof("Runs before %s", ctx.Path())
	ctx.Next()
}


5、启动并测试

5.1、启动项目

在 VS Code 终端输入以下命令并执行

# 启动项目
go run main.go

有以下信息代表启动成功

Iris Version: 12.2.0-beta6

Now listening on: http://localhost:8080
Application started. Press CTRL+C to shut down.

5.2、测试接口

5.2.1、用户登录

account = admin 并且 password = 123456,响应成功

在这里插入图片描述

响应结果增加 JWT 格式 token 字段

JSON Web Tokens (JWT) 在线解密

在这里插入图片描述

5.2.2、通用响应异常测试

请求头不添加 Authorization,响应失败

在这里插入图片描述

请求头里加入Authorization,响应成功

在这里插入图片描述
通过上述测试验证,项目达到预期目标,小伙伴自己赶紧动手试试吧。

6、每日一记

UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和随机数。

6.1、UUID 五种版本的区别

  • V1,基于mac地址、时间戳。

  • V2,based on timestamp,MAC address and POSIX UID/GID (DCE 1.1)

  • V3,Hash获取入参并对结果进行MD5。

  • V4,纯随机数。

  • V5,based on SHA-1 hashing of a named value。

6.2、UUID 两种包:

# 仅支持 V1 和 V4 版本
github.com/google/uuid

# 支持全部五个版本
github.com/gofrs/uuid

本文教程到此结束,有问题欢迎大家讨论。

实践是检验真理的唯一标准,一键送三连关注不迷路。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【强化学习论文合集】七.2017神经信息处理系统大会论文(NIPS2017)

强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题。 本专栏整理了近几年国际顶级会议中,涉及强化学习(Rein…

如何制作Docker镜像

一般有两种方式来制作自己的 Docker 镜像 基于现有的 Docker 镜像&#xff0c;安装自己的软件环境后&#xff0c;完成制作从零开始用 Dockerfile 来制作 宿主机环境 Ubuntu 18.04 x86_64 基于现有Docker镜像来制作 首先要获得一个基本的 Docker 镜像 使用 ”docker pull“…

【C++笔试强训】第三天

选择题 1.以下程序的输出结果是&#xff08;&#xff09; #include <stdio.h> int main() {char a[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, *p;int i;i 8;p a i;printf("%s\n", p - 3); }A 6 B 6789 C ‘6’ D 789 a是数组名&#xff0c;所以指向数组首元…

p4实现负载均衡

实验要求 基于简单版本的等成本多路径转发实现一种负载平衡。实现的交换机将使用两个表将数据包随机转发到两个目标主机之一第一个表将使用哈希函数&#xff08;应用于由源和目标IP地址、IP协议以及源和目标TCP端口组成的5元组&#xff09;来选择两个主机中的一个第二个表将使…

gnn神经网络是什么,gnn神经网络代码

1、gnn什么意思? 指图形神经网络。 生物神经网络主要是指人脑的神经网络&#xff0c;它是人工神经网络的技术原型。人脑是人类思维的物质基础&#xff0c;思维的功能定位在大脑皮层&#xff0c;后者含有大约10^11个神经元。 每个神经元又通过神经突触与大约103个其它神经元…

永磁同步电机(PMSM)磁场定向控制(FOC)及Matlab/Simulink仿真分析

文章目录前言一、FOC的基本原理二、坐标变换2.1.Clark坐标变换2.2.Park坐标变换三、永磁同步电机在同步旋转坐标系下的数学模型四、永磁同步电机磁场定向控制Matlab/Simulink仿真分析4.1.电压开环控制4.1.1.仿真电路分析4.1.2.仿真结果分析4.2.电流闭环控制4.2.1.仿真电路分析4…

两个月吃透阿里P9推荐260页SpringBoot2企业应用实战pdf入职定P6+

前言 都说程序员工资高、待遇好&#xff0c; 2022 金九银十到了&#xff0c;你的小目标是 30K、40K&#xff0c;还是 16薪的 20K&#xff1f;作为一名 Java 开发工程师&#xff0c;当能力可以满足公司业务需求时&#xff0c;拿到超预期的 Offer 并不算难。然而&#xff0c;提升…

Ansys Zemax | 使用OpticStudio进行闪光激光雷达系统建模(下)

在消费类电子产品领域&#xff0c;工程师可利用激光雷达实现众多功能&#xff0c;如面部识别和3D映射等。尽管激光雷达系统的应用非常广泛而且截然不同&#xff0c;而“闪存激光雷达”解决方案适用于在使用固态光学元件的目标场景中生成可检测的点阵列。 凭借在针对小型封装获…

「Redis」08 主从复制

笔记整理自【尚硅谷】Redis 6 入门到精通 超详细 教程 Redis——主从复制 1. 简介 主机数据更新后根据配置和策略&#xff0c; 自动同步到备机的 master/slaver 机制&#xff0c;Master 以写为主&#xff0c;Slaver 以读为主。 主从复制的好处 读写分离&#xff0c;性能扩展…

利用styleSheet,避免js手动频繁修改样式

styleSheet是一个样式表&#xff0c;记录了一个html文件中<style>标签中的样式 &#x1f5a8;️体感styleSheet 对于下面一个html文件 <!DOCTYPE html> <html lang"en"><head><style>h1 {color: red;}div {color: green; }</styl…

VMware替换难?听听ZStack 的这3家制造业客户怎么说……

随着国家对信创转型的持续扶持&#xff0c;加之VMware原有的产品与客户云建设需求的契合度不高&#xff0c;越来越多的用户选择将自身的IT系统替换为国产化的信创产品&#xff0c;且已经在金融、交通、制造业、教育、房地产等领域落地出现从VMware迁移转型的成功案例。在此趋势…

Python实战案例,PyQt5+socket模块,Python制作小型桌面应用

前言 本文给大家分享的是如何通过用PyQt5制作小型桌面应用 PyQt概述 PyQt5是Qt框架的Python语言实现&#xff0c;由Riverbank Computing开发&#xff0c;是最强大的GUI库之一。PyQt提供了一个设计良好的窗口控件集合&#xff0c;每一个PyQt控件都对应一个Qt控件&#xff0c;…

js实现的在线绘图板,写字板

一个像素完美的基于web的MS Paint重制版和更多…试试看&#xff01; JS Paint高度逼真地再现了MS Paint的每个工具和菜单&#xff0c;甚至是鲜为人知的功能。 它支持主题、其他文件类型和辅助功能&#xff0c;如眼睛注视模式和语音识别。 在线demo地址在线绘图板&#xff0c;在…

CorelDRAW破解版是如何一步一步坑人的

如果你还在说破解版软件免费好用&#xff0c;那我真的要用一个朋友的例子和你讲讲破解版CorelDRAW的害人之处了。 小赵是我的朋友学设计的&#xff0c;学生党一枚苦于囊中羞涩经常上破解版的车。一次他接了一个设计LOGO的活儿&#xff0c;准备用CorelDRAW来做这个LOGO。于是从…

web安全之post注入和cookie注入

目录 环境 靶场11&#xff08;POST注入&#xff09; 靶场20&#xff08;cookie注入&#xff09; 环境 靶场&#xff1a;sqli-labs&#xff0c;工具&#xff1a;burp 靶场11&#xff08;POST注入&#xff09; 抓包修改请求体 uname-1 union select 1,2 #&passwd&s…

2023年天津仁爱学院高职升本科专业考试报考须知

2023年天津仁爱学院高职升本科专业考试报考须知 一、报名条件 1.报考我校2023年高职升本科各专业的考生&#xff0c;应符合天津市教育招生考试院制定的2023 年天津市高职升本科及我校专业考试有关报考条件&#xff0c;须完成2023年天津市高职升本科文化考试报名相关手续&#x…

【Web端智能聊天客服】之HTML、CSS、Bootstrap的讲解及实例(超详细必看 附源码)

觉得有帮助请点赞关注收藏~~~ 一、HTML基础 HTML全称是HyperText Markup Language 是网页的标准标记语言&#xff0c;使用HTML可以创建自定义网站或者模板网站&#xff0c;HTML代表超文本标记语言&#xff0c;主要用于描述网页结构&#xff0c;HTML页面元素体现浏览器如何显示…

商业智能BI在面向报表和模型开发时,有什么不同?

企业在面试商业智能BI技术开发人员&#xff0c;发现基本上90%的人分不清什么是面向报表开发&#xff0c;什么是面向模型开发&#xff0c;不明白这个问题背后的意思。10%左右的人稍微暗示一下&#xff0c;大概就懂你想了解的是什么了&#xff0c;这10%的是真正有过完整的数据仓库…

我是如何开始能写python爬虫的?给零基础入门Python小白一条清晰的学习路线

重要的事说三遍&#xff1a;不要从看书开始&#xff0c;不要从看书开始&#xff0c;不要从看书开始&#xff01;~~ 爬虫这么有意思的东西&#xff0c;看书多没有乐趣&#xff0c;从网上找个视频&#xff0c;直接跟着写&#xff0c;然后再根据视频&#xff0c;按照自己的想法写…

Polaris 和 dubbogo 全面对接,让微服务更简单

作者 | 邓正威&#xff0c;廖春涛&#xff08;春少&#xff09;&#xff0c;赵新&#xff08;花名 于雨&#xff09;背景概述什么是 PolarisPolaris 是腾讯开源的服务治理平台&#xff0c;致力于解决分布式和微服务架构中的服务管理、流量管理、配置管理、故障容错和可观测性问…