当前位置: 首页 > news >正文

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有更深的了解了~
强者应该也不会到这里来吧…

相关文章:

JWT包中的源码分析【Golang】

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

SpringBoot数据字典字段自动生成对应code和desc

效果:接口会返回orderType,但是这个orderType是枚举的类型(1,2,3,4),我想多返回一个orderTypeDesc给前端展示,这样前端就可以直接拿orderTypeDesc使用了。 1. 定义注解 …...

TencentOS 2.4 final 安装mysql8.0备忘录

准备 tencentOS 2.4 与Red Hat Enterprise Linux 7 是兼容的。 我们首先从oracle官网上下载mysql的源文件。 下载完成后你会得到以下文件: mysql84-community-release-el7-1.noarch.rpm 安装 首先你需要切换到root用户下。 1.安装源文件 yum localinstall my…...

Hadoop HA安装配置(容器环境),大数据职业技能竞赛模块A平台搭建,jdk+zookeeper+hadoop HA

HA概述 (1) 所谓HA(High Availablity),即高可用(7*24小时不中断服务)。 (2) 实现高可用最关键的策略是消除单点故障,HA严格来说应该分为各个组件的HA机制,H…...

使用javascript读取波形文件数据,并生成动态的波形图

代码如下: // HTML5 Canvas 动态波形生成器 // 使用JS读取波形文件并生成动态波形// 创建Canvas并设置上下文 const canvas document.createElement(canvas); canvas.width 800; canvas.height 400; document.body.appendChild(canvas); const ctx canvas.getC…...

服务器系统维护与安全配置

一、硬件系统的安全防护   硬件的安全问题也可以分为两种,一种是物理安全,一种是设置安全。   1、物理安全   物理安全是指防止意外事件或人为破坏具体的物理设备,如服务器、交换机、路由器、机柜、线路等。机房和机柜的钥匙一定要管理…...

大模型Weekly 03|OpenAI o3发布;DeepSeek-V3上线即开源!

大模型Weekly 03|OpenAI o3发布;DeepSeek-V3上线即开源!DeepSeek-V3上线即开源;OpenAI 发布高级推理模型 o3https://mp.weixin.qq.com/s/9qU_zzIv9ibFdJZ5cTocOw?token47960959&langzh_CN 「青稞大模型Weekly」,持…...

Spring Boot自定义注解获取当前登录用户信息

写在前面 在项目开发过程中,难免都要获取当前登录用户的信息。通常的做法,都是开发一个获取用户信息的接口。 如果在本项目中,多处都需要获取登录用户的信息,难不成还要调用自己写的接口吗?显然不用! 以…...

js创建二维空数组

js创建二维空数组 错误使用原因分析分析1举个例子:解释: 正确创建二维数组总结: 错误使用 今天在写代码的时候,需要创建一个长度为2的空二维数组,我当时第一反应就是,使用let arr new Array(2).fill([])&…...

AF3 checkpoint_blocks函数解读

checkpoint_blocks 函数实现了一种分块梯度检查点机制 (checkpoint_blocks),目的是通过分块(chunking)执行神经网络模块,减少内存使用。在深度学习训练中,梯度检查点(activation checkpointing)是一种显存优化技术。该代码可以: 对神经网络的块(blocks)按需分块,并对…...

下载并使用CICFlowMeter提取网络流特征(Windows版本)

CICFlowMeter简介 CICFlowMeter是一款流量特征提取工具,从原始的pcap包中聚合流,并提取流特征到csv格式的文件中。使用CICFlowMeter提取的特征有助于后续基于网络流的分析与建模 官方github地址:https://github.com/ahlashkari/CICFlowMete…...

深入了解JSON-LD:语义化网络数据的桥梁

目录 前言1. JSON-LD概述1.1 什么是JSON-LD?1.2 JSON-LD的优势 2. JSON-LD的核心概念2.1 上下文(Context)2.2 词汇表(Vocabulary)2.3 标注语言(Markup Language) 3. JSON-LD的语法与结构3.1 基本…...

分布式 IO 模块助力冲压机械臂产线实现智能控制

在当今制造业蓬勃发展的浪潮中,冲压机械臂产线的智能化控制已然成为提升生产效率、保障产品质量以及增强企业竞争力的关键所在。而分布式 IO 模块的应用,正如同为这条产线注入了一股强大的智能动力,开启了全新的高效生产篇章。 传统挑战 冲压…...

webrtc源码编译【linux/安卓】

编译webrtc库 环境ubuntu22.04 推荐在linux里运行一个docker容器,在新环境里搭建。 准备工作 #我使用了下面的安装命令。目前知道的必须需要的 git python3 unzip ninja jdk file lsb-release apt install -y git curl build-essential python3 python3-pip pyt…...

亚矩阵云手机

亚矩阵云手机是一个集成了云计算、大数据、人工智能和边缘计算等先进技术的云平台,它通过ARM虚拟化技术在云端运行手机应用,提供了全面、高效且稳定的服务。以下是对亚矩阵云手机的详细解析: 技术基础与架构 1.ARM虚拟化技术:亚矩阵云手机基于ARM服务器和…...

Flink状态编程

Flink处理机制的核心就是“有状态的流处理”,在某些情况下,一条数据的计算不仅要基于当前数据自身,还需要依赖数据流中的一些其他数据。这些在一个任务中,用来辅助计算的数据我们就称之为这个任务的状态。 一、按键分区状态&…...

【Django篇】--动手实现路由模块化与路由反转

一、路由模块化 在一个Django项目中,由于功能类别不同,因此需要将不同功能进行模块化设计。在Django项目中模块化设计则需要将不同模块封装为对应的app模块,每一个模块中涉及到的路由则也需要进行模块化设计,才能更好的让整个项目…...

多元统计分析练习题3

从总体 A A A 和 B B B 中分别抽取 n 10 n10 n10 个样本 假设 A , B A,B A,B 协方差矩阵相同,并且服从多元正态分布 计算得到的样本均值和样本离差阵分别为 X ‾ A ( 1 , 2 , 3 ) T , V B d i a g ( 1 , 1 , 1 ) X ‾ B ( 1.5 , 2.5 , 3.5 ) T , V B d i…...

windows remote desktop service 远程桌面RDS授权激活

windows remote desktop service 远程桌面RDS授权激活 功能介绍:操作步骤:1、添加远程桌面授权服务2、添加远程桌面授权许可 功能介绍: 本文以 windows Server 2016为例,系统默认远程桌面连接数是2个用户,如果多余两个…...

6-pandas数据读取

前言 一、分组聚合 1.groupby使用: groupby() 是 pandas 库中用于对数据进行分组操作的一个非常重要的方法。 import pandas as pddata {城市: [北京, 上海, 广州, 北京, 上海, 广州],人口: [2154, 2424, 1303, 2154, 2424, 1303],年龄: [25, 30, 35, 25, 30, 3…...

【Logback详解】

Logback详解 Logback 是一个用于 Java 应用的日志框架,它由 Log4j 的创始人 Ceki Glc 创建。Logback 分为三个模块:logback-core、logback-classic 和 logback-access。logback-classic 模块实现了 SLF4J (Simple Logging Facade for Java) API&#xf…...

Flume的概念和原理

一、Flume的概念 1、flume 作为 cloudera 开发的实时日志收集系统 2、flume一个分布式、可靠、和高可用的海量日志采集、聚合和传输的系统。支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种…...

初始nginx

华子目录 nginx介绍nginx功能介绍基础特性web服务相关功能nginx进程结构web请求处理机制 nginx进程间通信nginx启动与http连接建立http处理过程 nginx模块介绍nginx命令演示 nginx介绍 nginx是免费的、开源的、高性能的HTTP和反向代理服务器、邮件代理服务器、以及TCP/UDP代理服…...

vulnhub靶场 Empire LupinOne

使用命令查看靶机ip,访问ip arp-scan -l 使用御剑扫描一下子域名,但是没有获取到什么有用的信息 这是一个Apache文档,没有什么用 紧接着我们尝试暴力破解,这里推荐使用ffuf工具暴力破解目录,kali自带的ffuf扫描速度贼快 参数解释…...

6-Gin 路由详解 --[Gin 框架入门精讲与实战案例]

Gin 是一个用 Go 语言编写的 HTTP Web 框架,以其高性能和简洁的 API 而闻名。它提供了一套强大的路由功能,使得开发者可以轻松地定义 URL 路由规则,并将这些规则映射到具体的处理函数(handler)。以下是关于 Gin 路由的…...

使用Lodash工具库的orderby和sortby进行排序的区别

简介 _.orderBy 和 _.sortBy 是 Lodash 库中用于排序数组的两个函数。 区别 _.orderBy 允许你指定一个或多个属性来排序,并为每个属性指定排序方向(升序或降序)。默认所有值为升序排,指定为"desc" 降序&#xff0c…...

CSS面试题|[2024-12-24]

1.说一下CSS的盒模型 在HTML页面中的所有元素都可以看成是一个盒子 盒子的组成:内容content、内边距padding、边框border、外边距margin 盒模型的类型: 标准盒模型 margin border padding content IE盒模型 margin content(包括border p…...

flask-admin 在modelview 视图中重写on_model_change 与after_model_change

背景: 当我们在使用flask-admin进行WEB开发时应该第一时间想到的是竟可能使用框架推荐的modelView模型,其次才是自定义模型 baseview,因为只有modelview模型下开发才能最大限度的提高效率。 制作: 1、在modelview视图下框架会通过默认视图…...

Excel粘贴复制不完整的原因以及解决方法

在数据处理和分析的过程中,Excel无疑是不可或缺的工具。然而,在使用Excel进行复制粘贴操作时,有时会遇到粘贴不完整的情况,这可能会让人感到困惑和烦恼。本文将深入探讨Excel粘贴复制不完整的原因、提供解决方案,并给出…...

【深度学习环境】NVIDIA Driver、Cuda和Pytorch(centos9机器,要用到显示器)

文章目录 一 、Anaconda install二、 NIVIDIA driver install三、 Cuda install四、Pytorch install 一 、Anaconda install Step 1 Go to the official website: https://www.anaconda.com/download Input your email and submit. Step 2 Select your version, and click i…...