首页 > 其他分享 >golang原生实现JWT

golang原生实现JWT

时间:2023-04-29 19:11:17浏览次数:40  
标签:原生 return err nil JWT golang token string

golang原生实现JWT

JWT(JSON Web Token)是一种基于JSON的安全令牌,可以用于在不同系统之间传输认证信息。在Go中实现JWT验证,可以通过标准库crypto/hmaccrypto/sha256encoding/base64来编写自己的JWT。

获取Token

我们在此封装一个JWT的struct结构体(由于除了Payload,其他很大可能不会在其他地方用到,所以不公开)

type JWT struct {
    header    string
    Payload   string
    signature string
}

将base64的编码封装一下方便使用

func encodeBase64(data string) string {
	return base64.RawURLEncoding.EncodeToString([]byte(data))
}

我们封装一个用来生成签名的方法

func generateSignature(key []byte, data []byte) (string, error) {
	// 创建一个哈希对象
	hash := hmac.New(sha256.New, key)
	// 将要签名的信息写入哈希对象中 hash.Write(data)
	_, err := hash.Write(data)
	if err != nil {
		return "", err
	}
	// hash.Sum()计算签名,在这里会返回签名内容
	// 将签名经过base64编码生成字符串形式返回。
	return encodeBase64(string(hash.Sum(nil))), nil
}

我们封装一个CreateToken用于生成Token(该方法的参数key为(生成签名所使用的密钥))

func CreateToken(key []byte, payloadData any) (string, error) {
    // 标准头部
	header := `{"alg":"HS256","typ":"JWT"}`
    // 将负载的数据转换为json
	payload, jsonErr := json.Marshal(payloadData)
	if jsonErr != nil {
		return "", fmt.Errorf("负载json解析错误")
	}
    
    // 将头部和负载通过base64编码,并使用.作为分隔进行连接
	encodedHeader := encodeBase64(header)
	encodedPayload := encodeBase64(string(payload))
	HeaderAndPayload := encodedHeader + "." + encodedPayload
    
    // 使用签名使用的key将传入的头部和负载连接所得的数据进行签名
	signature, err := generateSignature(key, []byte(HeaderAndPayload))
	if err != nil {
		return "", err
	}
    
    // 将token的三个部分使用.进行连接并返回
	return HeaderAndPayload + "." + signature, nil
}

解析Token

我们封装一个解析token的方法

func ParseJwt(token string, key []byte) (*JWT, error) {
    // 分解规定,我们使用.进行分隔,所以我们通过.进行分隔成三个字符串的数组
	jwtParts := strings.Split(token, ".")
    // 数据数组长度不是3就说明token在格式上就不合法
	if len(jwtParts) != 3 {
		return nil, fmt.Errorf("非法token")
	}

	// 分别拿出
	encodedHeader := jwtParts[0]
	encodedPayload := jwtParts[1]
	signature := jwtParts[2]

	// 使用key将token中的头部和负载用.连接后进行签名
    // 这个签名应该个token中第三部分的签名一致
	confirmSignature, err := generateSignature(key, []byte(encodedHeader+"."+encodedPayload))
	if err != nil {
		return nil, fmt.Errorf("生成签名错误")
	}
    // 如果不一致
	if signature != confirmSignature {
		return nil, fmt.Errorf("token验证失败")
	}

	// 将payload解base64编码
	dstPayload, _ := base64.RawURLEncoding.DecodeString(encodedPayload)
    // 返回我们的JWT对象以供后续使用
	return &JWT{encodedHeader, string(dstPayload), signature}, nil
}

实际使用

我们构造一个用户的结构体

type UserInfo struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

我们这次使用123456作为密钥简单的验证一下

var Key []byte = []byte("12346")

在此我们构造一个验证的中间件

func jwtConfirm(context *gin.Context) {
	// 登录不需要token
	if context.Request.RequestURI == "/login" {
		return
	}
	// 拿出token
	token := context.GetHeader("Token")
    // 进行解析验证
	jwt, err := utils.ParseJwt(token, Key)
	if err != nil {
		context.JSON(200, gin.H{
			"msg": err.Error(),
		})
        // 有问题就流产掉(我也不知道怎么翻译好了,香蕉猫.jpg)
		context.Abort()
	}
    // 验证通过就将负载返回回去
	context.JSON(200, gin.H{
		"payload": jwt.Payload,
	})
}

我们在此基础上就可以使用了,这边使用gin框架简单的测试一下

func main() {
    // 使用默认路由
	router := gin.Default()
    // 注册中间件
	router.Use(jwtConfirm)
    
    // 简单做两个服务
	router.POST("/login", func(context *gin.Context) {

		// 接收用户参数
		var userInfo UserInfo = UserInfo{}
        // 使用jsonbind接收
		bindErr := context.ShouldBindJSON(&userInfo)
		if bindErr != nil {
			context.JSON(200, gin.H{
				"msg": bindErr.Error(),
			})
		}
		// 使用密钥做出token
		jwt, err := utils.CreateJwt(Key, userInfo)
		if err != nil {
			fmt.Println(err)
		}

         // 我们将token直接返回用于测试
		context.JSON(200, gin.H{
			"token": jwt,
		})
	})

	router.GET("/doing")

	router.Run()
}

测试结果

image-20230429184039413.png

我们拿到了token

我们现在去试一下如果不带token的结果

image-20230429184135724.png

我们试一下携带错误token的情况

image-20230429184233619.png

我们最后测试一下正确的token

image-20230429184635149.png


结语

在实际环境中会使用更复杂的情况进行使用(例如密钥会更加复杂,会在pyload中设置失效时间等等),但是生成token和解析token的操作和上述的操作差别不大

标签:原生,return,err,nil,JWT,golang,token,string
From: https://www.cnblogs.com/coderDreams/p/17364380.html

相关文章

  • 火山引擎云原生数据仓库 ByteHouse 技术白皮书 V1.0 (Ⅵ)
     更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群近日,《火山引擎云原生数据仓库ByteHouse技术白皮书》正式发布。白皮书简述了ByteHouse基于ClickHouse引擎的发展历程,首次详细展现ByteHouse的整体架构设计及自研核心技术,为云原生数据......
  • 火山引擎云原生数据仓库 ByteHouse 技术白皮书 V1.0 (Ⅵ)
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群近日,《火山引擎云原生数据仓库ByteHouse技术白皮书》正式发布。白皮书简述了ByteHouse基于ClickHouse引擎的发展历程,首次详细展现ByteHouse的整体架构设计及自研核心技术,为云原生数据......
  • EF Core 中原生SQL、存储过程、视图的使用
    包括EFCore中原型Sql的执行,包括存储过程和视图数据处理方法,同时包括参数化执行sql语句的注意事项。原生Sql查询原生sql查询使用如下两个方法进行,查询的结构只能映射到dbset关联的对象类型DBSet.FromSqlRaw()DBSet.FromSqlInterpolated()可以使用部分linq扩展方法.FromSqlR......
  • golang —— 实现接口的结构体调用方式
    在接口上调用方法时,必须有和方法定义时相同的接收者类型或者是可以根据具体类型P直接辨识的:指针方法可以通过指针调用值方法可以通过值调用接收者是值的方法可以通过指针调用,因为指针会首先被解引用接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址将一个......
  • 5月7日【飞天Club × 云原生技术实践营—飞天免费计划实践专场】开启报名
    点击此处或扫描文末二维码免费报名参加活动福利:1、云谷游园,开发者面对面2、云原生产品免费试用,ApsaraClouder函数计算免费考证3、完成动手体验并通过考证即可现场获得精美礼品一份!4、现场多轮抽奖,精美好礼送不停!时间: 2023.5.7(周日)14:00-17:00地点:阿里巴巴云谷园区2号楼......
  • Golang GMP原理(1)
    GolangGMP原理(1)概念梳理线程线程一般指内核级线程,核心如下:操作系统的最小调度单元创建销毁调度由内核完成,cpu要完成内核态与用户态的转换可充分利用多核,实现并行协程协程线程对应协程,又称为用户级线程,核心点如下:与线程存在映射关系,为M:1创建、销毁、调度在用户......
  • 云原生实践路径
    CNCF(CloudNativeComputingFoundation)提供了实现云原生的路径图:https://raw.githubusercontent.com/cncf/trailmap/master/CNCF_TrailMap_latest.png 容器化上云的第一步:将你的应用程序容器化推荐工具:Docker,基本上是唯一选择, https://www.docker.com/CI/......
  • golang常用的http请求操作
    之前用python写各种网络请求的时候写的非常顺手,但是当打算用golang写的时候才发现相对来说还是python的那种方式用的更加顺手,习惯golang的用法之后也就差别不大了,下面主要整理了常用的通过golang发起的GET请求以及POST请求的代码例子golang发起GET请求基本的GET请求//基本的GE......
  • Golang每日一库之bcrypt
    本文官方文档:https://pkg.go.dev/golang.org/x/crypto/bcrypt前言之前讲过JWTTokenhttps://www.cnblogs.com/zichliang/p/17303759.htmlJWT呢是信息是经过数字签名的,因此可以被验证和信任。然后今天就来说说密码学,我们在做鉴权做用户处理时会把密码存储到数据库中,但是......
  • 在Golang中使用Testify mock框架
    1.前言2.实现代码3.Mock和测试4.Mock无参方法5.Mock带参数的方法6.Mock带参数的方法,但是参数具体内容非测试重点7.Mock带参数的方法,并校验实际参数8.Mockery9.参考1.前言我使用golang已经有一段时间了,但直到最近我才终于明白如何在golang测试中进行对象......