Go-gin-example 第二部分 jwt验证

文章目录

  • 使用 JWT 进行身份校验
    • jwt知识点补充
      • 认识JWT
      • TOKEN是什么
      • jwt的使用场景
      • jwt的组成
        • header
        • payload
        • signature
    • 下载依赖包
    • 编写 jwt 工具包
    • jwt中间件编写
    • 如何获取`token` 编写获取`token`的Api
      • models逻辑编写
      • 路由逻辑编写
      • 修改路由逻辑
    • 验证token
      • 将中间件接入`Gin`
      • 功能验证模块

续接上部分

使用 JWT 进行身份校验

在前面几节中,我们已经基本的完成了 API’s 的编写,但是,还存在一些非常严重的问题,例如,我们现在的API是可以随意调用的,这显然还不安全全,在本文中我们通过 jwt-go (GoDoc)的方式来简单解决这个问题。

jwt知识点补充

jwt官网

认识JWT

JSON Web TokenJWT)是一个开放标准(RFC 7519),它定义了一种紧凑和自包含的方式,用于在各方之间作为JSON对象安全地传输信息。

作为标准,它没有提供技术实现,但是大部分的语言平台都有按照它规定的内容提供了自己的技术实现,所以实际在用的时候,只要根据自己当前项目的技术平台,到官网上选用合适的实现库即可。

TOKEN是什么

Token,其实就是服务端生成的一串加密字符串、以作客户端进行请求的一个“令牌”

在这里插入图片描述

jwt的使用场景

以下是JWT两种使用场景:

授权:这是使用 JWT 的最常见的使用场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用。

信息交换JWT是在各方之间安全传输信息的比较便捷的方式。由于 JWT 可以签名(例如,使用公钥/私钥对),因此可以确定发送者是否是在您的授权范围之内。并且,由于签名是使用标头和有效负载计算的,因此还可以验证内容是否未被篡改

jwt的组成

这是一个JWT的token串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

其实这一串是经过加密之后的密文字符串,中间通过.来分割。每个.之前的字符串分别表示JWT的三个组成部分:HeaderPayloadSignature

header

Header的主要作用是用来标识,通常是两部分组成

typtype 的简写,令牌类型,也就是JWT

algAlgorithm 的简写,加密签名算法。一般使用HS256jwt官网提供了12种的加密算法

然后通过base64编码,将明文编码,防止在传输过程中能直接一眼看出明文并符合多种传输协议

payload

也称为JWT claims

payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为claims,它的一个“属性值对”其实就是一个claim,每一个claim的都代表特定的含义和作用

claims有三类:

  • 保留claims:主要包括iss发行者、exp过期时间、sub主题、aud用户等。
keyname说明
iss发送者标识颁发 JWT 的发送主体
sub主题标识 JWT 的主题
aud接收者标识 JWT 所针对的接收者。每个在处理 JWT 的主体都必须使用受众声明中的值来标识自己。如果处理的主体在存在此声明时未将自己标识为声明中的值,则必须拒绝 JWT
exp到期时间标识不得接受 JWT 进行处理的过期时间。该值必须是日期类型,而且是1970-01-01 00:00:00Z 之后的日期秒。
nbfjwt的开始处理的时间标识 JWT 开始接受处理的时间。该值必须是日期。
iatjwt发出的时间标识 JWT 的发出的时间。该值必须是日期。
jtijwt id令牌的区分大小写的唯一标识符,即使在不同的颁发者之间也是如此。

保留claimjwt标准中规定的claim,验证方式已经定义好

  • 公共claims:定义新创的信息,比如用户信息和其他重要信息。(使用较少)
  • 私有claims:用于发布者和消费者都同意以私有的方式使用的信息。

明文实例:

{"sub": "12344321","name": "Mars酱", // 私有claims"iat": 1516239022
}

base64加密后:

eyJzdWIiOiIxMjM0NDMyMSIsIm5hbWUiOiJNYXJz6YWxIiwiaWF0IjoxNTE2MjM5MDIyfQ
signature

Signature 部分是对HeaderPayload两部分的签名,作用是防止 JWT 被篡改。这个部分的生成规则主要是是公式(伪代码)是:

Header中定义的签名算法alg(base64编码(header) + "." + base64编码(payload),secret//在服务端加密使用的密钥
)

JWT如果从字面上理解感觉是基于JSON格式用于网络传输令牌。实际上,JWT是一种紧凑的Claims声明格式,,常见的场景如HTTP授权请求头参数和URI查询参数。JWT会把Claims转换成JSON格式,而这个JSON内容将会应用为JWS结构的有效载荷或者应用为JWE结构的(加密处理后的)原始字符串,通过消息认证码(Message Authentication Code或者简称MAC)和/或者加密操作对Claims进行数字签名或者完整性保护。

下载依赖包

go get -u github.com/dgrijalva/jwt-go

编写 jwt 工具包

我们需要编写一个jwt的工具包,我们在pkg下的util目录新建jwt.go,写入文件内容:

package utilimport ("time"jwt "github.com/dgrijalva/jwt-go""github.com/kingsill/gin-example/pkg/setting"
)// 加载配置文件中设置的密钥
var jwtSecret = []byte(setting.JwtSecret)// Claims 定义claims结构体
type Claims struct {Username string `json:"username"`Password string `json:"password"`jwt.StandardClaims
}func GenerateToken(username, password string) (string, error) {nowTime := time.Now()expireTime := nowTime.Add(3 * time.Hour)//创建 CustomClaims 结构体,用来封装 jwt 信息claims := Claims{username,password,jwt.StandardClaims{ExpiresAt: expireTime.Unix(),Issuer:    "gin-blog",},}//创建 header和payload部分tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)//得到完整的token字符串,这里为加入签名signature部分token, err := tokenClaims.SignedString(jwtSecret)return token, err
}func ParseToken(token string) (*Claims, error) {//解码过程tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {return jwtSecret, nil})//验证是否时间过期if tokenClaims != nil {if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {return claims, nil}}return nil, err
}

在这个工具包,我们涉及到:

  • NewWithClaims(method SigningMethod, claims Claims)method对应着SigningMethodHMAC struct{},其包含SigningMethodHS256、SigningMethodHS384、SigningMethodHS512三种crypto.Hash方案
  • func (t *Token) SignedString(key interface{}) 该方法内部生成签名字符串,再用于获取完整、已签名的token
  • func (p *Parser) ParseWithClaims 用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Token
  • func (m MapClaims) Valid() 验证基于时间的声明exp, iat, nbf,注意如果没有任何声明在令牌中,仍然会被认为是有效的。并且对于时区偏差没有计算方法

jwt中间件编写

中间件相关知识
自定义中间件
有了jwt工具包,接下来我们要编写要用于Gin的中间件,我们在middleware下新建jwt目录,新建jwt.go文件,写入内容:

package jwtimport ("net/http""time""github.com/gin-gonic/gin""github.com/kingsill/gin-example/pkg/e""github.com/kingsill/gin-example/pkg/util"
)// 自定义中间件
func JWT() gin.HandlerFunc {//返回.context函数return func(c *gin.Context) {var code intvar data interface{}//默认是正确状态code = e.SUCCESS//参数查询url中token关键字token := c.Query("token")//如果为空,则进行相关提示if token == "" {code = e.INVALID_PARAMS} else { //如果右token,进行token的解析claims, err := util.ParseToken(token)if err != nil { //如果解析出错,相关提示code = e.ERROR_AUTH_CHECK_TOKEN_FAIL} else if time.Now().Unix() > claims.ExpiresAt { //如果解析出来token已过期,则也有相关提示code = e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT}}//后续处理,如果之前步骤有错误,进行一下操作if code != e.SUCCESS {c.JSON(http.StatusUnauthorized, gin.H{"code": code,"msg":  e.GetMsg(code),"data": data,})//放弃后续中间件的执行,即如果有错,后续中间件都不执行c.Abort()return}//如果没错,放行	next前为请求中间件,next后为相应中间件c.Next()}
}

如何获取token 编写获取token的Api

那么我们如何调用它呢,我们还要获取Token呢?

models逻辑编写

models下新建auth.go文件,写入内容:

package models// jwt验证的 数据库相关操作// Auth 用户对应的struct模型
type Auth struct {ID       int    `gorm:"primary_key" json:"id"`Username string `json:"username"`Password string `json:"password"`
}// CheckAuth 根据用户名和密码查询用户是否存在
func CheckAuth(username, password string) bool {var auth Authdb.Select("id").Where(Auth{Username: username, Password: password}).First(&auth)if auth.ID > 0 {return true}return false
}

}

路由逻辑编写

routers下的api目录新建auth.go文件,写入内容:

package v1import ("log""net/http""github.com/astaxie/beego/validation""github.com/gin-gonic/gin""github.com/kingsill/gin-example/models""github.com/kingsill/gin-example/pkg/e""github.com/kingsill/gin-example/pkg/util"
)// 定义我们验证用户所需的信息,同时定义valid验证的预定信息,即一定要有并且最大字符数为50
type auth struct {Username string `valid:"Required; MaxSize(50)"`Password string `valid:"Required; MaxSize(50)"`
}func GetAuth(c *gin.Context) {//参数查询,?=模式,获取用户名和密码username := c.Query("username")password := c.Query("password")//通过设立结构体验证的valid验证,即所需并且最大为50个字符valid := validation.Validation{}a := auth{Username: username, Password: password}ok, _ := valid.Valid(&a)//建立存储信息的map key类型为string,val类型为任意值data := make(map[string]interface{})//设置默认code为参数错误code := e.INVALID_PARAMS//通过前序验证,继续通过数据库进行验证if ok {//通过数据库进行验证isExist := models.CheckAuth(username, password)//如果通过数据库验证if isExist {//创建token令牌token, err := util.GenerateToken(username, password)if err != nil { //如果生成令牌失败code = e.ERROR_AUTH_TOKEN} else { //生成token成功,进行存储data["token"] = tokencode = e.SUCCESS}} else { //没通过数据库验证code = e.ERROR_AUTH}} else { //没通过前序验证for _, err := range valid.Errors {log.Println(err.Key, err.Message)}}//json相应c.JSON(http.StatusOK, gin.H{"code": code,"msg":  e.GetMsg(code),"data": data,})
}

修改路由逻辑

我们打开routers目录下的router.go文件,修改文件内容(新增获取 token 的方法):
增添一句 r.GET("/auth", api.GetAuth),放在路由组之外

func InitRouter() *gin.Engine {
r := gin.New()r.Use(gin.Logger())r.Use(gin.Recovery())gin.SetMode(setting.RunMode)r.GET("/auth", api.GetAuth)//------------------------apiv1 := r.Group("/api/v1")
{
...
}return r
}

验证token

获取token API 方法就到这里啦,让我们来测试下是否可以正常使用吧!

重启服务后,用GET方式访问http://127.0.0.1:8000/auth?username=test&password=test123456 ,查看返回值是否正确

{"code": 200,"data": {"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJwYXNzd29yZCI6InRlc3QxMjM0NTYiLCJleHAiOjE3MDUzMzk2MzUsImlzcyI6Imdpbi1ibG9nIn0.4zblfic9MdOvrg4TF9Li8nfw3FSBq3rGgKqnJnDFXYY"},"msg": "ok"
}

我们有了tokenAPI,也调用成功了

将中间件接入Gin

修改路由分组模块语句,apiv1 := r.Group("/api/v1").Use(jwt.JWT()),将jwt验证加入全局路由

...apiv1 := r.Group("/api/v1")
apiv1.Use(jwt.JWT())
{
...
}
...

当前目录结构

go-gin-example/
├── conf
│   └── app.ini
├── main.go
├── middleware
│   └── jwt
│       └── jwt.go
├── models
│   ├── article.go
│   ├── auth.go
│   ├── models.go
│   └── tag.go
├── pkg
│   ├── e
│   │   ├── code.go
│   │   └── msg.go
│   ├── setting
│   │   └── setting.go
│   └── util
│       ├── jwt.go
│       └── pagination.go
├── routers
│   ├── api
│   │   ├── auth.go
│   │   └── v1
│   │       ├── article.go
│   │       └── tag.go
│   └── router.go
├── runtime

到这里,我们的JWT编写就完成啦!

功能验证模块

我们来测试一下,再次访问

  • http://127.0.0.1:8000/api/v1/articles
  • http://127.0.0.1:8000/api/v1/articles?token=23131
    正确的反馈应该是
{"code": 400,"data": null,"msg": "请求参数错误"
}{"code": 20001,"data": null,"msg": "Token鉴权失败"
}

我们需要访问http://127.0.0.1:8000/auth?username=test&password=test123456 ,得到token

再用包含tokenURL 参数去访问我们的应用 API

  • 这里的问题即为创建的token还需要自己复制粘贴,不能自动取用等

访问http://127.0.0.1:8000/api/v1/articles?token=eyJhbGci... ,检查接口返回值

{"code": 200,"data": {"lists": [],"total": 0},"msg": "ok"
}

验证正确,文章列表取决于数据库内容

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

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

相关文章

交友脱单盲盒源码,纸条广场,支持单独抽取/连抽/同城

源码介绍 交友脱单盲盒源码,纸条广场,单独抽取/连抽/同城。 盲 盒交友脱单系统源码包含了学校、爱好、城市、地区、星座等 等信息,具有首页轮转广告和页面美化功能。 首页提供了两款 连抽和高质量底部连抽的选项,并且可以在后台…

如何通过ISPC使用Xe(核显)进行计算

我一直以为 ISPC 的 Xe 是只包含独立显卡的,比如 A770 这些,没想到看了眼文档是可以使用核显的,但只能在 Linux 和 Windows 上,macOS 不行,就想试试看。 写本文是因为 ISPC 已经出现了三四个版本的大改,但…

基于SSM的网上挂号系统的设计与实现

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

每日一练:LeeCode-102、二又树的层序遍历【二叉树】

本文是力扣LeeCode-102、二又树的层序遍历 学习与理解过程,本文仅做学习之用,对本题感兴趣的小伙伴可以出门左拐LeeCode。 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点&…

【设计模式-3.3】结构型——享元模式

说明:说明:本文介绍设计模式中结构型设计模式中的,享元模式; 游戏地图 在一些闯关类的游戏,如超级玛丽、坦克大战里面,游戏的背景每一个关卡都不相同,但仔细观察可以发现,其都是用…

java基于Spring Boot的灾害应急救援评估调度平台

灾害应急救援平台的目的是让使用者可以更方便的将人、设备和场景更立体的连接在一起。能让用户以更科幻的方式使用产品,体验高科技时代带给人们的方便,同时也能让用户体会到与以往常规产品不同的体验风格。(1)鉴于该系统是一款面向…

C++ 设计模式之外观模式

【声明】本题目来源于卡码网(题目页面 (kamacoder.com)) 【提示:如果不想看文字介绍,可以直接跳转到C编码部分】 【简介】什么是外观模式 外观模式Facade Pattern , 也被称为“⻔⾯模式”,是⼀种结构型设计模式&#…

WordPress如何修改旧文章的发布日期让其变成新文章发布?

我们个人网站发展一段时间后,可能就不懂得发布什么内容了,这个时候可以考虑翻看以前的旧文章,必要时对其进行适当修改,然后修改它的发布日期变成当前日期重新发布,这样就会变成新文章重新出现在我们首页的文章列表中。…

基于Pytorch的身份证及其他证件检测矫正模型应用

前言 在做身份证和其他证件识别的时候,图片基本都不是摆正的状态,此时在进行OCR文字识别的提取文字信息的时候会出现很多误差,如何将证件摆正,再进行OCR文字识别就可以大大提高准确率。 准备工作 1、Python环境,在P…

tda7294功放电路图大全

简易电子管功放电路图(一) 6P3P单端A类电子管功放电路图 如图为6P3P单端A类电子管功放电路图。VT1、VT2直流通路串联。VT1构成普通的三极管共阴放大器,VTr2构成阴极输出器,对VT1而言VT2是一个带电流负反馈的高阻负载。音频信号由…

Leetcode2696. 删除子串后的字符串最小长度

Every day a Leetcode 题目来源:2696. 删除子串后的字符串最小长度 解法1:暴力 暴力做法是不断把 AB 和 CD 去掉,直到 s 中没有 AB 和 CD 为止。 代码: /** lc appleetcode.cn id2696 langcpp** [2696] 删除子串后的字符串最…

漏洞复现-金和OA jc6/servlet/Upload接口任意文件上传漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复,敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

关于浮点数的四舍五入问题

最近有关注到,在C/C中,对于浮点数的四舍五入,与实际的有一些出入,我打算今天总结一下,并解释一下这是为啥, 好了,下面进入正题,都是干货哦,认真看完,留下你的…

西米支付:到底什么是NFT(数字藏品支付通道)(NFT支付通道)

NFT到底指的是什么呢? 数字藏品的实际意义在于它们打破了传统艺术品的物质形态束缚。数字藏品可以通过虚拟现实和区块链技术进行创作、展示和交易。它们不仅可以满足人们对艺术品的审美需求,还可以成为一种投资和资产保值增值的方式。数字藏品的实际意义…

排序——归并排序

文章目录 基本思想递归版本思路代码实现 非递归版思路代码实现 特性结果演示 基本思想 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子…

开发实践6_缓存^中间件

以下学习 朔宁夫 开发工程师 课程。 缓存可提高程序响应速度。数据库缓存(可过期)/ Redis缓存(Key:Value)/ Memcacheed缓存/ 程序层缓存。 一 缓存 1. 数据库缓存 创建缓存数据表 // python manage.py createcachetable cache_table setting // # 缓存配置 CACHES {def…

12.云原生之kubesphere中应用部署方式

云原生专栏大纲 文章目录 k8s中应用部署Kubernetes常用命令 kubesphere中可视化部署应用创建工作负载服务暴露 helm部署应用helm命令行部署应用kubesphere中使用应用仓库 k8s中应用部署 在k8s中要想部署应用,需要编写各种yaml文件,一旦应用依赖比较复杂…

蓝桥杯备赛 | 洛谷做题打卡day2

​ 蓝桥杯备赛 | 洛谷做题打卡day2 嵌套循环yyds!! 题目来源:洛谷P2670 [NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 n n n 行 m m m 列的雷区中有一些格子含有地雷&am…

如何去开发直播电商系统小程序

明确你的直播电商系统的功能和特性,包括用户注册、商品展示、购物车、支付结算、直播功能、评论互动等。根据需求确定系统的基本架构和主要模块。 技术选型:选择适合你的直播电商系统的技术栈。考虑前端框架(如React、Vue.js)、后…

C语言:编译和链接

目录 一:翻译环境和运行环境 二:翻译环境 2.1预处理(预编译) 2.2编译 2.2.1 词法分析: 2.2.2语法分析 2.2.3语义分析 2.3 汇编 三:运行环境 一:翻译环境和运行环境 在ANSI C的任何一种…