首页 > 其他分享 >jwt在go中的应用

jwt在go中的应用

时间:2023-12-01 18:03:10浏览次数:35  
标签:jwt JWT 认证 token cookie 签名 应用 go

官网

JWT

什么是 JWT

在现代的 Web 应用开发中,目前已经有大半部分的应用都是使用的 jwt 的方式来做登录鉴权功能,那么什么是 jwt 呢?

  • JSON Web Token(JWT) 是一个开放标准 RFC 519,它定义了一种紧凑且自包含的方式,用于作为 JSON 对象在各方面之间安全地传输信息;
  • JWT 是一个数字签名,生成的信息是可以验证并被信任的;
  • 它可以使用 HMAC 算法或使用 RSAECDSA 的公钥/私钥对对 jwt 进行签名;
  • JWT 是目前最流行的跨域认证解决方案;

什么时候用到 JWT

这里有一些场景下可以使用到 jwt,具体如下:

  • Authorization: 这是使用 jwt 最常见的场景。一次登录,之后的每次请求都会包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用 JWT 的一个特性,因为它开销很小,并且能够轻松地跨不同的域使用;
  • 信息交换: jwt 是在各方之间安全传输信息的好方法。因为 jwt 可以进行签名,例如使用 公钥/私钥对,所以您可以确定发送者就是就是本人。此外,由于签名是使用 headerpayload 计算的,因此您还可以验证内容是否未被篡改;

JWT的优点

  • 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
  • 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
  • 更适用CDN:可以通过内容分发网络请求服务端的所有资料
  • 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
  • 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

JWT通常的认证流程

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同 lll.zzz.xxx 的字符串
  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  4. 前端在每次请求时将 JWT Token 放入HTTP请求头中的 Authorization 属性中(解决XSS和XSRF问题)
  5. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  6. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

为什么要用JWT

传统Session认证的弊端

我们知道HTTP本身是一种无状态的协议

这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,认证通过后HTTP协议不会记录下认证后的状态,那么下一次请求时,用户还要再一次进行认证。

因为根据HTTP协议,我们并不知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在用户首次登录成功后,在服务器存储一份用户登录的信息。

这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了。

这是传统的基于session认证的过程

然而,传统的session认证有如下的问题:

  • 每个用户的登录信息都会保存到服务器的session中,随着用户的增多,服务器开销会明显增大
  • 由于session是存在与服务器的物理内存中,所以在分布式系统中,这种方式将会失效。虽然可以将session统一保存到Redis中,但是这样做无疑增加了系统的复杂性,对于不需要redis的应用也会白白多引入一个缓存中间件
  • 对于非浏览器的客户端、手机移动端等不适用,因为 session 依赖于 cookie,而移动端经常没有 cookie
  • 因为 session 认证本质基于 cookie ,所以如果 cookie 被截获,用户很容易收到跨站请求伪造攻击。并且如果浏览器禁用了 cookie,这种方式也会失效
  • 前后端分离系统中更加不适用,后端部署复杂,前端发送的请求往往经过多个中间件到达后端,cookie 中关于 session 的信息会转发多次
  • 由于基于Cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用

JWT认证的优势

对比传统的session认证方式,JWT的优势是:

  • 简洁:JWT Token数据量小,传输速度也很快
  • 因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持
  • 不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务
  • 单点登录友好:使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
  • 适合移动端应用:使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端

因为这些优势,目前无论单体应用还是分布式应用,都更加推荐用JWT token的方式进行用户认证

JWT结构

在其紧凑形式中,JWT 由点 . 分割的三个部分组成,分别是:

  • 标头(Header)
  • 有效载荷(Payload)
  • 签名(Signature)

因此,一个 JWT 通常如下所示: xxxxx.yyyyy.zzzzz

具体组成

JWTString=Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header)+'.'+base64UrlEncode(payload),密钥)

标头(Header)

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);

typ属性表示令牌的类型,JWT令牌统一写为JWT。

最后,使用Base64 URL算法将上述JSON对象转换为字符串保存

{
  "alg": "HS256",
  "typ": "JWT"
}

有效载荷(Payload)

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。

它包含声明,声明是关于实体(通常是用户)和附加数据的陈述。

声明分为三种类型: 注册声明、公共声明和私有声明。

  1. 注册声明: 这些是一组预定义的声明,具有特定的含义并进行了标准化, JWT指定七个标准字段供选择:

    iss:发行人
    exp:到期时间
    sub:主题
    aud:用户
    nbf:在此之前不可用
    iat:发布时间
    jti:JWT ID用于标识该JWT
    
  2. 公共声明: 这些是由 jwt 的使用者定义的自定义声明,它们没有标准化。公共声明应该以避免与其他声明冲突的方式进行定义。这些声明用于携带关于实体的附加信息或提供特定域应用程序的细节。

    例如:

    {
       "name": "Helen",
       "admin": true,
       "userid": 1
    }
    
  3. 私有声明: 这些是由共享 JWT 的各方之间的私有协议定义和使用的自定义声明。它们不适用于其他方使用或理解;


一个有效的 payload 的例子是:

{
  "exp": 1701390069,
  "iss": "somebody",
  "name": "Helen",
  "admin": true,
  "userid": 1
}

请注意,默认情况下JWT是未加密的,因为只是采用base64算法,拿到JWT字符串后可以转换回原本的JSON数据,任何人都可以解读其内容,因此不要构建隐私信息字段,比如用户的密码一定不能保存到JWT中,以防止信息泄露。JWT只是适合在网络中传输一些非敏感的信息

签名(Signature)

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密钥(secret)。

该密码仅仅为保存在服务器中,并且不能向用户公开。

然后,使用header中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名

HMACSHA256(base64UrlEncode(header)+'.'+base64UrlEncode(payload),密钥)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用 . 分隔,就构成整个JWT对象


注意JWT每部分的作用,在服务端接收到客户端发送过来的JWT token之后:

  • headerpayload 可以直接利用 base64 解码出原文,从 header 中获取哈希签名的算法,从 payload 中获取有效数据
  • signature 由于使用了不可逆的加密算法,无法解码出原文,它的作用是校验token有没有被篡改。
    服务端获取header中的加密算法之后,利用该算法加上 secretKeyheaderpayload 进行加密,比对加密后的数据和客户端发送过来的是否一致。
    注意 secretKey 只能保存在服务端,而且对于不同的加密算法其含义有所不同,一般对于 MD5 类型的摘要加密算法,secretKey 实际上代表的是盐值

JWT的种类

其实JWT(JSON Web Token)指的是一种规范,这种规范允许我们使用JWT在两个组织之间传递安全可靠的信息,JWT的具体实现可以分为以下几种:

  • nonsecure JWT:未经过签名,不安全的JWT
  • JWS:经过签名的JWT
  • JWE:payload部分经过加密的JWT

nonsecure JWT

未经过签名,不安全的JWT。其 header 部分没有指定签名算法

{
  "alg": "none",
  "typ": "JWT"
}

并且也没有 Signature 部分

JWS

JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。

创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。

加密的算法一般有2类:

  • 对称加密:secretKey指加密密钥,可以生成签名与验签
  • 非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)

JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

  • HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
  • RSASSA【RSA签名算法(非对称)】(RS256/RS384/RS512)
  • ECDSA【椭圆曲线数据签名算法(非对称)】(ES256/ES384/ES512)

GO中的简单应用

安装 JWT 库

在 Go 项目中使用 JWT 需要先安装 JWT 库,可以使用以下命令安装:

go get -u github.com/golang-jwt/jwt/v5

将其导入代码中:

import "github.com/golang-jwt/jwt/v5"

创建JWT

package main

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// CustomClaims 自定义声明类型 并内嵌jwt.RegisteredClaims
// jwt包自带的jwt.RegisteredClaims只包含了官方字段
// 假设我们这里需要额外记录一个username字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type CustomClaims struct {
	// 可根据需要自行添加字段
	UserID               int64  `json:"user_id"`
	Username             string `json:"username"`
	jwt.RegisteredClaims        // 内嵌标准的声明
}


// 密钥
var secretKey = "abcdef"

// GenToken 生成JWT
func GenToken(userId int64, username string) (string, error) {
	// 创建一个我们自己声明的数据
	claims := CustomClaims{
		userId,
		username, // 自定义字段
		jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 定义过期时间
			Issuer:    "somebody",                                         // 签发人
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 生成签名字符串
	return token.SignedString([]byte(secretKey))
}

func main() {
	res, err := GenToken(123, "abc")
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(res)
	}
}

在上面的例子中,使用 jwt.MapClaims 定义了需要存储在 JWT 中的数据,使用 jwt.NewWithClaims() 方法创建了 JWT,然后使用 SignedString() 方法生成了签名字符串。

运行后的结果

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiYWJjIiwiaXNzIjoic29tZWJvZHkiLCJleHAiOjE3MDE1MDk1Mjh9.rJiXzq3z8MwmSLHndn08XlDJAkWcy_1w1Qw4WcgEdMA

验证JWT

在 Go 项目中,可以使用 JWT 库的 jwt.Parse() 方法验证 JWT

例如:

package main

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// CustomClaims 自定义声明类型 并内嵌jwt.RegisteredClaims
// jwt包自带的jwt.RegisteredClaims只包含了官方字段
// 假设我们这里需要额外记录一个username字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type CustomClaims struct {
	// 可根据需要自行添加字段
	UserID               int64  `json:"user_id"`
	Username             string `json:"username"`
	jwt.RegisteredClaims        // 内嵌标准的声明
}


// 密钥
var secretKey = "abcdef"

func ParseToken(tokenString string) (*CustomClaims, error) {
	// 解析token
	var mc = new(CustomClaims)
	token, err := jwt.ParseWithClaims(tokenString, mc, func(token *jwt.Token) (i interface{}, err error) {
		return []byte(secretKey), nil
	})
	if err != nil {
		if strings.Contains(err.Error(), "could not JSON decode header") {
			fmt.Println("token可能被篡改,头解析失败")
		}
		if err.Error() == "token signature is invalid: signature is invalid" {
			fmt.Println("令牌签名无效")
		}
		if err.Error() == "token has invalid claims: token is expired" {
			fmt.Println("令牌已过期")
		}
		return nil, err
	}
	// 对token对象中的Claim进行类型断言
	if token.Valid { // 校验token
		fmt.Println("令牌有效")
		return mc, nil
	}
	return nil, errors.New("无效的令牌")
}
func main() {
	token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoiYWJjIiwiaXNzIjoic29tZWJvZHkiLCJleHAiOjE3MDE1MDk1Mjh9.rJiXzq3z8MwmSLHndn08XlDJAkWcy_1w1Qw4WcgEdMA"
	res, err := ParseToken(token)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(res)
	}
}

运行的结果

令牌有效
&{123 abc {somebody  [] 2023-12-02 17:32:08 +0800 CST <nil> <nil> }}

在上面的例子中,使用 jwt.Parse() 方法解析 JWT,并在回调函数中检查签名算法和返回签名字符串。然后检查 token 是否有效,并从中提取出需要的数据。

以上就是在 Go 项目中使用 JWT 的基本步骤。需要注意的是,在实际应用中,需要更加严格地设置 JWT 的有效期、密钥等参数,以确保安全性。

在线解析的工具

  1. JSON Web Tokens (JWT) 在线解密, 开发工具箱 - JWT 在线解密 。这个网页提供了jwt解密功能。功能比较简单,上手很容易。

  2. JWT parser, IT Tools - Handy online tools for developers 。这个网页也提供了jwt解密功能,比较上一个网页,这个网页的解密结果里会把payload里面的unix时间戳转成我们能看懂的时间格式。

  3. ToolTT在线加解密, JWT Token在线解析解码 - ToolTT在线工具箱JWT Token在线编码生成 - ToolTT在线工具箱 。这个网站的体验又比前两个网页的体验要好。额外支持jwt令牌的生成,令牌生成的选项比较细致,可读性墙方便操作。美中不足,不允许修改令牌的签发时间。

  4. bejson在线工具, jwt解密/加密 - bejson在线工具 。如果您正在寻找一种可靠且高效的处理JWT的方式,它无疑是一个很好的选择。这个网页除了提供了全面的JWT加密和解密功能外,还提供了签名验证功能

问题

如果只修改 payload 不修改头部和签名会怎样

头部和签名通常是不可更改的,因为签名是基于头部、载荷和密钥计算得出的结果,用于验证 token 的完整性和真实性。如果头部和签名被更改,验证过程会失败,因此头部和签名部分通常需要保持不变。
如果 JWT token 中的头部和签名不作修改,但是修改了载荷部分,那么验证 token 的过程仍然会成功,因为签名仍然与原始载荷匹配。这可能会导致潜在的安全问题,因为后端是基于 token 中的载荷信息去做出一些决策或者判断的,类似于要根据你的id去查找相对应的用户信息。而恶意修改载荷可能会导致不正确的行为或授权问题。

总结

  • 由于 HeaderPayload 是基本使用 Base64URL 能进行编码解码的,所以 HeaderPayload 的内容是等同于公开的,谁拿到 JWT,都能知道内容;
  • 由于 Signature 的生成方式,JWT 一旦生成,没有 Secret 的人是没法篡改 payload 的。因为改了 payload 后,你需要 secret 对新的 payload 进行签名;

综上所述,jwt 一经发出,就无法修改了。一旦泄漏,任何人都可以获得该 token 的所有权限。为了减少盗用,jwt 的有效期应该设置得比较短,对于一些比较重要的权限,使用时应该再次对用户进行认证。

标签:jwt,JWT,认证,token,cookie,签名,应用,go
From: https://www.cnblogs.com/guangdelw/p/17870616.html

相关文章

  • Django补4
    过滤器写一个过滤器---》一堆内容---》经过过滤器后---》把关键词屏蔽#自定义过滤器{{变量|过滤器名字}}编写步骤1注册app2在某个app下:创建templatetags模块(模块名只能是templatetags)3在包下写一个py文件,随便命名4在py文件中:写入fromdjangoimporttemplateregister=t......
  • .NET Core|--调用C++库|--docker环境下让web api应用程序调用C++类库
    前言#前提安装docker环境~启动docker~#多说一句,为什么我要搞这个一个镜像,既包含gcc开发环境,又包含.NET开发环境我的api应用程序是基于.NET写的,但是我的这个api程序,又要调用c++的一些东西,特别是涉及一些画图之类的,所以就需要gcc的开发环境,最终搞了这么一......
  • 智慧安防三大信息技术:云计算、大数据及人工智能在视频监控EasyCVR中的应用
    说到三大信息技术大家都很清楚,指的是云计算、大数据和人工智能,在人工智能(AI)快速发展的当下,例如常见的大数据分析、人工智能芯片生产的智能机器人等等,在工作、生活、教育、金融、科技、工业、农业、娱乐等各个领域随处可见,那么三大信息技术在智能监控中又有哪些应用呢?今天我们就结......
  • GORM学习
    Day1:GORM入门1.环境的安装在项目文件的terminal中输入下面两条命令进行gorm安装gogetgorm.io/driver/mysqlgogetgorm.io/gorm2.安装好之后使用以下代码进行检测,其中的地址拼接是重点"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s",us......
  • Google Play 结算系统
    技术GooglePlay。供用户下载应用及其他数字商品的在线商店。GooglePlay管理中心。提供界面,供您将应用发布到GooglePlay的平台。GooglePlay管理中心还会显示您的应用详情,包括您通过GooglePlay销售的任何商品或内容。GoogleCloud控制台。用于管理后端API(例如Google......
  • Go--命名规则
    在Go语言中,项目名和文件名的命名规则有一些建议和惯例。以下是一些常见的规则和最佳实践:项目名:项目名应该简短、有意义,并能够清晰地表达项目的目的或功能。项目名通常使用小写字母,使用连字符或下划线分隔单词。项目名不应包含特殊字符或空格。项目名应尽量避免与现有的库......
  • Google Play 允许区块链游戏和 NFT 应用进入平台
    为GameFi用户在地域分布与手机机型分布方面与GooglePlay 有众多契合之处:地域分布:东南亚地区用户占比最大,2022年上半年东南亚用户占比达到41%其次是北美和西欧地区用户,2022年上半年占比分别为16%和15%发展中国家用户占比也在快速增长,如菲律宾、越南、印度等机......
  • 振弦采集仪在岩土工程中的应用与优势
    振弦采集仪在岩土工程中的应用与优势振弦采集仪是一种先进的岩土工程测试设备,可以用于进行土壤动力学性质的试验和监测。它在岩土工程中的应用非常广泛,具有许多优势,可以大幅提高岩土工程的监测效率和测试准确性。下面,我们来详细探讨振弦采集仪在岩土工程中的应用和优势。 一......
  • 综合材料在初中学科美术中的应用——LW
    研究背景及意义近年来,综合材料越来越多的出现在中学美术教育活动当中,而校外美术教育又由于其课堂学生人数少、材料丰富、创作时间充足等优势成为中学综合材料美术教育活动的主要场所。值得注意的是,不同年龄阶段中学的综合素质发展状况是具有较大差异的,这也就导致了不同年龄段学生......
  • MongoTemplate操作MongoDB
    集成简介spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb,MongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodb,MongoRepository的缺点是不够灵活,MongoTemplate正好可以弥补不足。搭建开发环境1、创建springbo......