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 框架:让你更深入地理解 MVC 架构设计》源代码进阶,如果只想了解 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 toolimport "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 toolimport ("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 toolimport ("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"`   // 用户IDUserName 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(),              // 唯一 IDSubject:   "主题",                   // 主题Issuer:    "发行人",                  // 发行人IssuedAt:  DataTimeUnix(dataTime), // 发布时间,单位:秒NotBefore: DataTimeUnix(dataTime), // 生效时间,单位:秒ExpiresAt: DataTimeUnix(expTime),  // 过期时间,单位:秒},}tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)// 该方法内部生成签名字符串,再用于获取完整、已签名的tokentokenString, _ := tokenClaims.SignedString(jwtSecret)return tokenString
}/*** <h2>解析 JWT 的 Token</h2>*/
func ParseWithClaims(tokenString string) (*DClaims, error) {// 用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*TokentokenClaims, 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 controllerimport ("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 的 TokensysUser.Token = tool.SignedJwtTokenBySysUser(sysUser)// 响应成功ctx.JSON(common.ResponseSuccess(sysUser))
}

4.2.5、业务异常自定义

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

package constantimport "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 routerimport (// 自己业务 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) {// 从请求头中获取Tokentoken := 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-beta6Now 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

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

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

GoWeb 项目实战:使用 Iris 框架构建一个安全可靠的 API 服务(附 JWT 认证实现)相关推荐

  1. 用html实现抽奖大转盘,【项目实战】用CSS实现一个抽奖转盘(附详细代码+思路)...

    原标题:[项目实战]用CSS实现一个抽奖转盘(附详细代码+思路) 效果 基本是用CSS实现的,没有用图片,加一丢丢JS. 完全没有考虑兼容性. 首先画一个转盘 < htmllang= " ...

  2. php mysql弹幕_如何使用PHP构建一个高性能的弹幕后端服务

    如何使用PHP构建一个高性能的弹幕后端服务 随着WEB2.0的流行,现在很多网站都流行使用"弹幕"这种形式来实现互动. 弹幕(barrage),中文流行词语,原意指用大量或少量火炮 ...

  3. 从零开始构建一个高可靠的RabbitMQ镜像集群

    从零开始构建一个高可靠的RabbitMQ镜像集群 1.集群环境节点规划如表所示: 1 集群构建 1.停止MQ服务,首先停止3个节点的服务的命令如下: service rabbitmq-server s ...

  4. 微信小程序开发15 项目实战 基于云开发开发一个在线商城小程序

    在学完前 4 个模块之后,我相信你会对微信小程序的开发有一个全新的认识.在前面 3 个模块中,俊鹏分别从微信小程序内在的运行原理,小程序工程化开发以及具体实践层面,深度讲解了微信小程序开发所必要的知识 ...

  5. 微前端vue项目实战 -- 乾坤qiankun框架 (一)

    背景: 公司研发的项目主要是 GIS地图为基础的管理系统,由主体项目(管理业务模块)+ GIS地图应用,由于 GIS地图应用模块 会在多个地方使用,所以单独构建一个项目,分别在不同的业务场景中引用.交 ...

  6. 限时免费 | 人工智能项目实战训练营,给你一个成为AI算法工程师的机会

    在当前的就业环境下,人工智能工程师的高薪优势凸显,可却有不少人陷入了瓶颈期,不知道该朝着哪个方向发展自己,"掌握理论知识但缺乏实战经验"成为了求职晋升路上的短板,硬件设备的不足和自 ...

  7. 360使用什么php框架,[项目实战] 马震宇出品360问答系统项目实战 基于HDPHP框架

    学了一大堆理论及基础知识,找工作之前总得先实战一下吧? 抽出一点时间来吧,各位,好好的研究一下自己学习的相关项目 相信在你找工作的时候一定会起到莫大的帮助哦! 这里都是免费发布给大家学习,不做任何盈利 ...

  8. 【业余开发笔记】用gradle构建一个简单的rest api

    以下是一些gradle构建项目的使用笔记,由于自己对maven也算太了解,所以不谈区别和优劣了,就简单总结一下关于gradle的使用好了. 以下是关于用gradle构建一个以spring boot为框 ...

  9. 如何构建一个生产环境的推荐系统?(附代码)

    导读:现在互联网上的内容很多,我们可能每天都会接受来自不同消息.例如,电商网站.阅读博客.各类新闻文章等.但是,这些消息并不是所有的内容你都感兴趣,可能你只对技术博客感兴趣,或者某些新闻感兴趣等等.而 ...

最新文章

  1. 生成对抗网络(GAN)的数学原理全解
  2. visual MySQL 教程_Visual Studio操作MySQL的详细步骤
  3. LeetCode 344 反转字符串
  4. Android studio的设置界面介绍
  5. 高中数学40分怎么办_高二数学不会,准高三该怎么办?40分到高考140如何逆袭?...
  6. 算法学习之剑指offer(六)
  7. 局域网https安全证书解决方案mkcert
  8. SQL2005数据库备份如何还原到SQL2000数据库中
  9. 三角网格剖分工具 Triangle 安装及使用
  10. 统计天数oracle,Oracle计算连续天数,计算连续时间,Oracle连续天数统计
  11. 数据科学必备用Python进行描述性统计数据分析详解
  12. 授人以渔:分享我的算法学习经验
  13. BurpSuite 通过google浏览器抓取https流量包
  14. Sketch Less for More: On-the-Fly Fine-Grained Sketch Based Image Retrieval (CVPR 2020 Oral)
  15. Day124.分布式事务:Seata、2PC两段式、代码补偿TCC、本地消息表、MQ事物消息
  16. mac macbook应用清单
  17. 企业智能化转型meetup回顾|开源BI AI助力企业转型之旅三阶段
  18. 替换Android手机的开机动画,安卓技术宅系列之修改手机开机动画
  19. EasyPay(易支付)Android超简单微信支付宝支付接入
  20. 用计算机弹很多的歌,抖音用计算器弹奏音乐曲谱大全 极乐净土、体面和成都等歌曲...

热门文章

  1. 基于Python的面向数据安全治理的数据内容智能发现与分级分类
  2. 《领导梯队》:4星。企业中六个层级的领导的必知必会和必须不能做的事情。...
  3. MPAS-A模式的介绍
  4. COMSOL案例学习(1):顶盖驱动方腔流
  5. JavaScript 三元运算符原理 +案例
  6. 开始着手用Python写一个游戏脚本(一)
  7. 【洛谷P2423】双塔
  8. 《比较视野下的外国文学》
  9. 精排模型-从MLP到行为序列:DIN、DIEN、MIMN、SIM、DSIN
  10. python找不到自定义包的解决办法