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

用大白话讲明白JWT

JWT,是JSON Web Token的缩写,目前最流行的跨域认证解决方案。

为什么需要JWT?

一般的互联网用户认证流程如下:

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。
  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这就是经典的cookie-session认证模式,cookie保存在客户端,持有session-id,而session保存在服务端,持有用户信息和权限、过期时间等信息。

这种模式简洁易用,还能保证信息安全。

但是随着架构更新成服务器集群跨域的服务导向架构,就要求实现session共享,否则不能实现session共享。一般来说,能够通过中间件实现session共享,比如redis。

举例来说,某系统有两个后端服务,都是集群部署,分别为服务A和服务B,用户在A服务登录之后,携带session-id请求B,要求用户能够通过B的登录验证,并且能获取用户信息。
大家都知道,session默认是存储的服务端的硬盘中,上面的场景中,用户登录的session应该会存储在服务集群A的某个节点上,那么用户访问任何其他的节点或服务都要重新登录。
简易架构图
最常见的解决方案就是利用中间件存储session。如上图中,若将session存储在redis中,那么用户登录之后,每个服务节点都能通过session-id查询到session,实现单点登录。

那么这种cookie-session模式有什么缺点呢?

  1. 客户端禁用cookie就会失效
  2. 跨域可能会导致cookie丢失
  3. 必须要有统一的中间件
  4. 中间件如果挂了,登录就会失效

优化方案

  1. 弃用cookie,直接将用户token存放在request header中,用户信息以token为key存放在redis
  2. 服务器索性不保存session数据了,直接将用户token和用户信息全都存在request header中,JWT就是此方案的代表

JWT的原理

怎么实现将用户信息存放在客户端,还能实现用户登录认证和权限认证?
我们可以将用户信息和用户权限和用户认证时间放一起生成一个json,如下

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2024年12月1日0点0分"
}

然后如果将这个字符串放在请求头中,那么服务端每次接收到请求都能知道用户信息和是否已登录等。
这样做的好处是服务器是无状态的,扩展性非常好。
但是请求头必须经过编码和网络传输,需要考虑乱码和URLEncode等问题。
所以我们要将上面的字符串使用Base64URL进行编码,规避这些问题。

新问题又来了,用户信息在客户端被篡改了怎么办?
我们再加一道签名(一种信息安全技术,此处不展开),防止信息篡改。

最终,上面的JSON经过这样魔改之后,就是所谓的JWT!它最终长这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyLlp5PlkI0iOiLlvKDkuIkiLCLop5LoibIiOiLnrqHnkIblkZgiLCLliLDmnJ_ml7bpl7QiOiIyMDI05bm0MTLmnIgx5pelMOeCuTDliIYifQ.fvBo5i2wXeYufnUQGLaJvtY6GdcqRhTgA5kc5S8HqLU

总结一下,JWT就是一串经过签名和编码的json对象,用户登录后由服务端生成,一般通过request header传输,轻松实现在不同平台和系统间信息交换和认证。

JWT官网: [https://jwt.io/]

JWT结构

JWT 有三个部分,依次如下。

Header(头部)
Payload(负载)
Signature(签名)

用英文点隔开,写成一行,就是下面的样子。
Header.Payload.Signature
JWT结构
下面依次介绍这三个部分。

Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

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

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2024年12月1日0点0分"
}

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把敏感信息放在这个部分。但是JWT的扩展性很好,我们可以先加密,再放入payload中。

在实际项目中,我一般将敏感信息使用DES加密,再放入JWT。

这个 JSON 对象也要使用 Base64URL 算法转成字符串。

Signature

Signature 部分是对前两部分的签名,防止数据篡改
简单聊一下签名,前面说过,jwt存放在客户端就会有被篡改的风险,尤其是用户权限部分。

报文签名就是防止信息篡改的主流方案,大致原理如下:

  • 加签:生成jwt的时候,将整个信息串通过一个秘钥计算加密,得到一个签名。秘钥仅保存在服务端。
  • 验签:等到下次服务端再接收到这个jwt的时候,再次拿信息串加密算出签名,和原来的签名进行比对,就能知道信息有没有被篡改了。
    算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

JWT的缺点

  • JWT 默认是不加密,需要我们手动加密
  • JWT无法作废,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  • JWT无法续签,因为JWT防篡改的特点,导致我们服务自己也没办法修改jwt的有效期,jwt一旦过期就无法续签
  • 放置在客户端还是会有被盗用的风险,建议有效时间尽量设置短一点

JWT的使用

几乎所有的编程语言都有jwt的公共组件库,jwt的官网为我们介绍了一些常用的组件:https://jwt.io/libraries
这里以Java的依赖com.auth0 / java-jwt为例:

引入依赖

		<dependency>
			<groupId>com.auth0</groupId>
			<artifactId>java-jwt</artifactId>
			<version>3.18.2</version>
		</dependency>

生成JWT

    public static String genJwt() {
        Map<String, Object> headMap = Maps.newHashMap();
        headMap.put("alg", "HS256");
        headMap.put("type", "JWT");

        return JWT.create()
                // Header
                .withHeader(headMap)
                // Payload
                .withClaim("tgc", "tgcVal")
                .withClaim("token", "Token")
                .withClaim("name", "zhangsan")
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000*60*60*24))
                // 签名用的secret
                .sign(Algorithm.HMAC256("secret"));
    }

验证JWT

	JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("secret")).build();    
	public static boolean verifyToken(String token) {
            try {
                Map<String, Claim> map = jwtVerifier.verify(token).getClaims();
                return true;
            } catch (JWTVerificationException e) {
                return false;
            }
        }

http://www.kler.cn/a/508401.html

相关文章:

  • 02.02、返回倒数第 k 个节点
  • 【Vim Masterclass 笔记16】S07L32 + L33:同步练习09 —— 掌握 Vim 宏操作的六个典型案例(含点评课内容)
  • python mysql库的三个库mysqlclient mysql-connector-python pymysql如何选择,他们之间的区别
  • ZooKeeper 核心概念与机制深度解析
  • 在 Fluent 网格划分中使用薄网格特征
  • 【无法下载github文件】虚拟机下ubuntu无法拉取github文件
  • Node.js - Express框架
  • Android 实现多语言功能
  • 从零开始:Gitee 仓库创建与 Git 配置指南
  • Temp123
  • 在MyBatis的XML映射文件中,<trim>元素所有场景下的完整使用示例
  • 12.接口和抽象类的区别
  • Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用能力
  • Oracle 可观测最佳实践
  • 异步 HTTP 请求
  • Spring Boot使用WebSocket
  • 游戏引擎学习第79天
  • 零基础构建最简单的 Tauri2.0 桌面项目 Star 88.4k!!!
  • 【STM32-学习笔记-8-】I2C通信
  • mayavi -> python 3D可视化工具Mayavi的安装
  • GoLang教程003:数据类型介绍
  • Java基础(二)
  • 基于 Spring Boot 和 Vue.js 的全栈购物平台开发实践
  • 正则表达式基础知识及grep、sed、awk常用命令
  • 【JVM-10】IBM HeapAnalyzer 工具使用指南:深入解析 Java 堆转储分析
  • 【微服务】SpringCloud 1-9章