JWT包中的源码分析【Golang】

前言

最近在学web编程的途中,经过学长提醒,在进行登陆(Login)操作之后,识别是否登陆的标识应该要放入authorization中,正好最近也在学鉴权,就顺便来看看源码了。

正文

1. 代码示例

在进行分析之前,先给予一段JWT生成和解析的代码示例,这样可以随着代码一步一步深入到源码层面来帮助我们进一步理解JWT这一部分知识。

package api//关于jwt的所有操作都在这里
import ("errors""fmt""github.com/dgrijalva/jwt-go""time"
)var mySigningKey = []byte("Bang dream Girls Band Part!114514Girls Band Cry!114514,It's My Go!!!!!")func GenerateJWT(username string) (string, error) {token := jwt.New(jwt.SigningMethodHS256)claims := token.Claims.(jwt.MapClaims)claims["username"] = usernameclaims["exp"] = time.Now().Add(time.Second * 30).Unix()ToKenSting, err := token.SignedString(mySigningKey)if err != nil {return "", err}return ToKenSting, nil
}func VerifyJWT(tokenString string) (string, error) {token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])}return mySigningKey, nil})if err != nil {return "", err}if claims, ok1 := token.Claims.(jwt.MapClaims); ok1 && token.Valid {username := claims["username"].(string)return username, nil}return "", errors.New("token invalid")
}

2. GenerateJWT

2.1 新建token

	token := jwt.New(jwt.SigningMethodHS256)

在函数开头,我们可以发现,我们使用了jwt包中的new,并传递了一个签名方法来新建了一个token
这个new函数又是如何实现的呢?在这里插入图片描述
我们可以发现,new就是返回了一个token类型的指针,而进一步向下查看NewWithClaims的实现在这里插入图片描述
则是直接返回了Token结构体的指针,其中的参数method.Alg()是直接返回了这个方法的Name:在这里插入图片描述

claims则是这个token的声明,也就是我们接下来需要写入数据的部分,

2.2 签名方法类型

刚刚我们提到了我们在创建token对象的时候需要传递一个签名方法,可以理解为我们的随后会以什么样的方式生成jwt字符串,很多人可能会对这个签名方法是什么东西感到疑惑,没有兴趣了解的可以跳过这一部分。

此处我们以SigningMethodHMAC为例子
在这里插入图片描述
这个所谓的签名方法实际上就是一个结构体,源代码中,我们可以看见,代码为这个结构体创建了256/384/512的实例对象,这就是我们真正可以访问的签名方法了,但是这几个实例对象作为结构体实例的参数又是什么呢?在这里插入图片描述
如图所示,在这个签名方法结构体所在的包中,利用init函数为这些实例对象赋予了值,而RegisterSigningMethod在这里插入图片描述
如图所示,RegisterSigningMethod的作用则是利用哈希表,将签名方法的名字映射到这一结构体的实体对象,相当于将签名方法注册到了实体中,从逻辑上来说,我觉得更美吧。

2.3 Claims

	claims := token.Claims.(jwt.MapClaims)claims["username"] = usernameclaims["exp"] = time.Now().Add(time.Second * 30).Unix()

这一步,对token中的Claims成员进行类型断言,并作为MapClaims类型赋予claims变量,此处由于Claims是一个接口,并且MapClaims实际上是一个哈希表,并且实现了Claims接口,所以可以转换,并且具有引用的作用,意思是修改claims的值就相当于修改了token.Claims的值。
在这里插入图片描述
这里先不对Raw以及没讲到的成员变量做说明,后面会提到。
在这里插入图片描述
在这里插入图片描述
我们可以看见MapClaims实际上是一个哈希表,而这个类型实现了Valid方法,所以就实现了Claims接口。

2.4 jwt字符串生成

	ToKenSting, err := token.SignedString(mySigningKey)

此处,则是我们生成jwt字符串的一步了
在这里插入图片描述
此处,除了两个需要注意的方法,其他都很简单,下面我们来看看这两个方法具体做了什么事情吧!

func (t *Token) SigningString() (string, error) {var err errorparts := make([]string, 2)for i, _ := range parts {var jsonValue []byteif i == 0 {if jsonValue, err = json.Marshal(t.Header); err != nil {return "", err}} else {if jsonValue, err = json.Marshal(t.Claims); err != nil {return "", err}}parts[i] = EncodeSegment(jsonValue)}return strings.Join(parts, "."), nil
}

首先是第一部分,创建了一个长度为2的string切片,随后我们对part进行遍历,当遍历到第一个部分时,代码将 Token 结构体中的头部信息(t.Header)序列化为 JSON 格式的字节切片。随后将其存储在parts[0]之中,当遍历到part第二部分则是将Claims部分进行序列化,并存入part[1],最后将这个切片用“.”连接起来。

随后来看看第二部分

func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {if keyBytes, ok := key.([]byte); ok {if !m.Hash.Available() {return "", ErrHashUnavailable}hasher := hmac.New(m.Hash.New, keyBytes)hasher.Write([]byte(signingString))return EncodeSegment(hasher.Sum(nil)), nil}return "", ErrInvalidKeyType
}

在这里首先对key进行类型断言,由于此处我们传入的是字节切片,所以不会出错,随后再进行判断这个签名方法的hash值是否合法,也就是看这个方法是否是正确的,这样理解便好,随后利用hmac包(对应了签名方法)中的New,并传入我们的Key,新建了一个哈希对象,随后将之前我们在外部生成的签名写入这个哈希对象,最后,通过调用 hasher.Sum(nil) 计算并返回结果,这个结果一般会是一个字符串形式的签名,用于生成最终的 JWT,而关于hash计算和生成其中的源码有点过于跑题了,就不在这里展开讲了。

	return strings.Join([]string{sstr, sig}, "."), nil

在生成签名的最后,将之前生成的字符串利用“.”连接在一起,这一步对于我们后面解析JWT字符串有大用,到这一步,就已经得到我们的JWT字符串了。

3. VerifyJWT

3.1 Parse

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])}return mySigningKey, nil})

我们解析JWT字符串需要创建一个token来存储我们的解析后的结果,而parse函数,就是解析我们的JWT字符串的大杀器了,在这个函数中,我们需要传入原始的JWT字符串和一个匿名函数,用来在Parse函数内部判断解析的方法时候正确。

func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {return new(Parser).Parse(tokenString, keyFunc)
}

在进入到函数内部之后,我们发现又是一层套娃,但是这一层新建了一个parser解析器,以此来进行调用解析

func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
}

继续进入下一步

func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {token, parts, err := p.ParseUnverified(tokenString, claims)if err != nil {return token, err}// Verify signing method is in the required setif p.ValidMethods != nil {var signingMethodValid = falsevar alg = token.Method.Alg()for _, m := range p.ValidMethods {if m == alg {signingMethodValid = truebreak}}if !signingMethodValid {// signing method is not in the listed setreturn token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)}}// Lookup keyvar key interface{}if keyFunc == nil {// keyFunc was not provided.  short circuiting validationreturn token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)}if key, err = keyFunc(token); err != nil {// keyFunc returned an errorif ve, ok := err.(*ValidationError); ok {return token, ve}return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}}vErr := &ValidationError{}// Validate Claimsif !p.SkipClaimsValidation {if err := token.Claims.Valid(); err != nil {// If the Claims Valid returned an error, check if it is a validation error,// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag setif e, ok := err.(*ValidationError); !ok {vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}} else {vErr = e}}}// Perform validationtoken.Signature = parts[2]if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {vErr.Inner = errvErr.Errors |= ValidationErrorSignatureInvalid}if vErr.valid() {token.Valid = truereturn token, nil}return token, vErr
}

终于来到了我们的源代码分析环节,

	token, parts, err := p.ParseUnverified(tokenString, claims)if err != nil {return token, err}

首先第一步,是对我们的token进行生成,我们需要进入函数内部来进行分析。

func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {parts = strings.Split(tokenString, ".")if len(parts) != 3 {return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)}token = &Token{Raw: tokenString}// parse Headervar headerBytes []byteif headerBytes, err = DecodeSegment(parts[0]); err != nil {if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)}return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}if err = json.Unmarshal(headerBytes, &token.Header); err != nil {return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}// parse Claimsvar claimBytes []bytetoken.Claims = claimsif claimBytes, err = DecodeSegment(parts[1]); err != nil {return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}dec := json.NewDecoder(bytes.NewBuffer(claimBytes))if p.UseJSONNumber {dec.UseNumber()}// JSON Decode.  Special case for map type to avoid weird pointer behaviorif c, ok := token.Claims.(MapClaims); ok {err = dec.Decode(&c)} else {err = dec.Decode(&claims)}// Handle decode errorif err != nil {return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}// Lookup signature methodif method, ok := token.Header["alg"].(string); ok {if token.Method = GetSigningMethod(method); token.Method == nil {return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)}} else {return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)}return token, parts, nil
}

是不是被吓到了?没关系,我带你来一步一步分析,其实重要的部分并不多。

	parts = strings.Split(tokenString, ".")if len(parts) != 3 {return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)}token = &Token{Raw: tokenString}

在这里,我们将原始JWT字符串利用我们之前设置的“.”分开,这时候我们便可以进行初步的判断这个接受的jwt字符串是否合法,判断之后,我们先将原始jwt字符串存入新建的token对象的raw成员中,这时候我们就需要提及我们之前没讲的raw成员,他就是我们token对象的原始jwt字符串。

	var headerBytes []byteif headerBytes, err = DecodeSegment(parts[0]); err != nil {if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)}return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}if err = json.Unmarshal(headerBytes, &token.Header); err != nil {return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}

这里看着很复杂,但是实际上抛开大量的if return,真正解析的部分并不多,这一部分就是将我们的jwt中的header部分进行解析,随后判断是否出错而已,随后将header中的信息存入token中。

	// parse Claimsvar claimBytes []bytetoken.Claims = claimsif claimBytes, err = DecodeSegment(parts[1]); err != nil {return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}dec := json.NewDecoder(bytes.NewBuffer(claimBytes))if p.UseJSONNumber {dec.UseNumber()}// JSON Decode.  Special case for map type to avoid weird pointer behaviorif c, ok := token.Claims.(MapClaims); ok {err = dec.Decode(&c)} else {err = dec.Decode(&claims)}// Handle decode errorif err != nil {return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}}

这一步也很简单,就是对我们的claims部分进行解析,然后放入token中就结束了。

	if method, ok := token.Header["alg"].(string); ok {if token.Method = GetSigningMethod(method); token.Method == nil {return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)}} else {return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)}

在最后从header获取方法的名字,判断一下是否合法就可以返回了。

接下来回到上一层源代码

	// Verify signing method is in the required setif p.ValidMethods != nil {var signingMethodValid = falsevar alg = token.Method.Alg()for _, m := range p.ValidMethods {if m == alg {signingMethodValid = truebreak}}if !signingMethodValid {// signing method is not in the listed setreturn token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)}}

继续下一步,p.ValidMethods是一个切片

type Parser struct {ValidMethods         []string // If populated, only these methods will be considered validUseJSONNumber        bool     // Use JSON Number format in JSON decoderSkipClaimsValidation bool     // Skip claims validation during token parsing
}

包含允许的签名方法。一个条件判断检查它是否为 nil,如果不为 nil,说明存在需要验证的方法集合,随后遍历这个集合,如果使用的签名方法在这个集合中,便可进行下一步。

	// Lookup keyvar key interface{}if keyFunc == nil {// keyFunc was not provided.  short circuiting validationreturn token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)}if key, err = keyFunc(token); err != nil {// keyFunc returned an errorif ve, ok := err.(*ValidationError); ok {return token, ve}return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}}

这一步很简单,就是调用我们传入的匿名函数,看看我们传入的签名方法是否和实际解析出来的的方法一致,如果一致,就进行下一步。

	// Validate Claimsif !p.SkipClaimsValidation {if err := token.Claims.Valid(); err != nil {// If the Claims Valid returned an error, check if it is a validation error,// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag setif e, ok := err.(*ValidationError); !ok {vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}} else {vErr = e}}}

p.SkipClaimsValidation就是一个bool值,这一步判断是否要跳过Claims的有效性验证,如果有效,进行下一步验证。

	// Perform validationtoken.Signature = parts[2]if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {vErr.Inner = errvErr.Errors |= ValidationErrorSignatureInvalid}if vErr.valid() {token.Valid = truereturn token, nil}return token, vErr
}

此处又要提到我们之前提到的token结构体中的signature成员了,此处将jwt中的签名部分赋值给signatrue(签名)
这一部分用于确保 JWT 的完整性和身份验证。签名是通过将前两个部分的编码后结果与密钥进行哈希计算而生成的。
此处直接调用token.Method.Verify(…)进行验证便好。

最后调用 valid 方法检查当前的验证错误状态。如果没有错误(即 vErr 是有效的),则将 token.Valid 设置为 true,直接返回就行了。

3.2 提取信息

最后,回到我们的VerifyJWT函数

	if claims, ok1 := token.Claims.(jwt.MapClaims); ok1 && token.Valid {username := claims["username"].(string)return username, nil}return "", errors.New("token invalid")

判断错误,提取信息就好了。

那么,关于源码的分析就到这里结束了,如果有什么问题,请留言~

结语

其实看源码的过程还是蛮有趣的,主要是可以感受着自己在一步一步变强的感觉(也说不准,代码的乐趣也很多),关于JWT的源码分析就到这里结束了,读完这篇文章,你应该对JWT有更深的了解了~
强者应该也不会到这里来吧…

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

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

相关文章

鸿蒙应用开发(1)

可能以为通过 鸿蒙应用开发启航计划(点我去看上一节) 的内容,就足够了,其实还没有。 可是我还是要告诉你,你还需要学习新的语言 -- ArkTS。 ,ArkTS是HUAWEI开发的程序语言。你需要学习这门语言。这会花费你…

python爬虫--小白篇【selenium自动爬取文件】

一、问题描述 在学习或工作中需要爬取文件资源时,由于文件数量太多,手动单个下载文件效率低,操作麻烦,采用selenium框架自动爬取文件数据是不二选择。如需要爬取下面网站中包含的全部pdf文件,并将其转为Markdown格式。…

超大规模分类(一):噪声对比估计(Noise Contrastive Estimation, NCE)

NCE损失对应的论文为《A fast and simple algorithm for training neural probabilistic language models》,发表于2012年的ICML会议。 背景 在2012年,语言模型一般采用n-gram的方法,统计单词/上下文间的共现关系,比神经概率语言…

latex 尖括号怎么写 编译出来是问号

首先引入\usepackage{amsmath}使用\langle \range, 如下: These knowledge graphs represent real-world facts in the form of triples of \(\langle entity, relationship, entity \rangle \)编译展示出来如下:

永磁同步电机控制算法--最大转矩电流比控制(牛顿迭代法)

一、原理介绍 搭建了基于牛顿迭代法的MTPA双闭环矢量控制系统 二、仿真验证 在MATLAB/simulink里面验证所提算法,采用和实验中一致的控制周期1e-4,电机部分计算周期为1e-6。仿真模型如下所示: 对直接公式计算法和牛顿迭代法进行仿真对比验…

[Win32/ATL]_[初级]_[处理WM_PAINT消息注意事项]

场景 在开发Win32/WTL程序时,遇到了使用CFolderDialog(atldlgs.h)打不开目录选择对话框的情况。具体表现是执行了窗口的DoModal,却没有窗口弹出来。 可以确定执行操作是在主线程,并不是工作线程。调试时暂停看堆栈,知道到DoModal方法里的SHB…

华为配置 之 RIP

简介: RIP(路由信息协议)是一种广泛使用的内部网关协议,基于距离向量算法来决定路径。它通过向全网广播路由控制信息来动态交换网络拓扑信息,从而计算出最佳路由路径。RIP易于配置和理解,非常适用于小型网络…

RCE-PLUS (学习记录)

源码 <?php error_reporting(0); highlight_file(__FILE__); function strCheck($cmd) {if(!preg_match("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i", $cmd)){return($cmd);}else{die("i hate this"…

Unity Mesh生成Cube

1. 配置一个Cube的每个面的数据 一共是6个面&#xff0c;每个面包含的数据包括4个顶点的相对顶点坐标&#xff08;Cube的中心为原点&#xff09;&#xff0c;法线方向&#xff0c;UV坐标&#xff0c;顶点渲染顺序&#xff0c;以及这个面用到的材质&#xff0c;因为这里是Top&am…

【YOLO算法改进】ALSS-YOLO:无人机热红外图像|野生动物小目标检测

目录 论文信息 论文创新点 1.自适应轻量通道分割和洗牌&#xff08;ALSS&#xff09;模块 2.轻量坐标注意力&#xff08;LCA&#xff09;模块 3.单通道聚焦模块 4.FineSIOU损失函数 摘要 架构设计 轻量高效网络架构 - ALSS模块 LCA模块 单通道聚焦模块 损失函数优…

【亚马逊云】基于Amazon EC2实例部署 NextCloud 云网盘并使用 Docker-compose 搭建 ONLYOFFICE 企业在线办公应用软件

文章目录 1. 部署EC2实例2. 安装 Docker 服务3. 安装docker-compose4. 创建Docker-compose文件5. 创建nginx.conf文件6. 运行docker-compose命令开始部署7. 访问ONLYOFFICE插件8. 访问NextCloud云盘9. 下载并启用ONLYOFFICE插件10. 上传文件测试11. 所遇问题12. 参考链接 1. 部…

使用爬虫技术获取网页中的半结构化数据

目录 前言1. 半结构化数据与爬虫技术简介1.1 半结构化数据的定义与特性1.2 爬虫技术的基本原理 2. 爬取半结构化数据的实现过程2.1 明确目标与准备2.2 发送HTTP请求2.3 解析网页内容2.4 动态内容的处理2.5 数据存储与清洗 3. 技术挑战与应对策略3.1 处理反爬机制3.2 提高爬取效…

win10、win11-鼠标右键还原、暂停更新

系统优化 win 10jihuo win 11jihuo鼠标右键还原暂停更新 update 2024.12.28win 10 jihuo winx&#xff0c;打开powershell管理员&#xff0c;输入以下命令,选择1并等待 irm https://get.activated.win | iex参考&#xff1a;https://www.bilibili.com/video/BV1TN411M72J/?sp…

【蓝桥杯选拔赛真题87】python输出字符串 第十五届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python输出字符串 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python输出字符串 第十五届蓝桥杯青少年组python比赛选拔赛真题详细解析…

有什么AI辅助阅读文献工具推荐?

AI的发展速度非常快&#xff0c;在很多方面都已经可以提供货真价实的辅助能力。 比如AI辅助阅读方面&#xff0c;可以获取、分析和理解大量文献资料。这可以帮助我们快速浏览和理解PDF文件和其他文档&#xff0c;提高我们的工作效率和学习效率&#xff0c;达到降本增效。 作为…

各个Spring Cloud版本有何主要差异

Spring Cloud 的各个版本之间确实存在一些关键差异&#xff0c;这些差异主要体现在功能更新、性能优化、对新技术的支持以及对旧有技术的替代等方面。 1. Spring Cloud Dalston 这是 Spring Cloud 的一个早期版本&#xff0c;它提供了微服务架构所需的基本组件&#xff0c;如服…

【翻译】审慎对齐:推理使更安全的语言模型成为可能

原文&#xff1a;https://arxiv.org/abs/2412.16339 出自OpenAI 摘要 随着大规模语言模型对安全关键领域的影响越来越大&#xff0c;确保它们可靠地遵守定义良好的原则仍然是一个基本挑战。本文提出慎思校准&#xff0c;一种新的范式&#xff0c;直接教模型安全规范&#xff…

1、ELK的架构和安装

ELK简介 elk&#xff1a;elasticsearch logstash kibana&#xff0c;统一日志收集系统。 elasticsearch&#xff1a;分布式的全文索引引擎的非关系数据库&#xff0c;json格式&#xff0c;在elk中存储所有的日志信息&#xff0c;架构有主和从&#xff0c;最少需要2台。 …

使用连字符容易出错,尽量使用驼峰式的

在<Select>组件中&#xff0c;存在一个拼写错误。在option - value属性中&#xff0c;正确的写法应该是option - value&#xff08;使用连字符&#xff09;或者optionValue&#xff08;如果是按照Vue组件属性的驼峰命名法&#xff09;&#xff0c;这里写成了option - val…