JWT身份验证

JWT知识点

jwt,全称 json web token, JSON Web 令牌是一种开放的行业标准 RFC 7519 方法,用于在两方之间安全地表示声明。

详情可以参考: hhttps://jwt.io/introduction

  1. 数据结构

JSON Web Token由三部分组成,它们之间用圆点.进行分割, 一个标准的JWT形如 xxx.yyy.zzz

●Header

●Payload

●Signature

1.1 header

即第一部分,由两部分组成:token的类型(JWT)和算法名称(比如:HMAC SHA256或者RSA等等)。

一个具体实例如

java 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}

然后,用Base64对这个JSON编码就得到JWT的第一部分

1.2 Payload

第二部分具体的实体,可以写入自定义的数据信息,有三种类型

●Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer 签发者), exp (expiration time 有效期), sub (subject), aud (audience)等。

●Public claims : 可以随意定义。

●Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明

如一个具体实例

java 复制代码
{
    "iss": "一灰灰blog",
    "exp": 1692256049,
    "wechat": "https://spring.hhui.top/spring-blog/imgs/info/wx.jpg",
    "site": "https://spring.hhui.top",
    "uname": "一灰"
}

对payload进行Base64编码就得到JWT的第二部分

1.3 Signature

为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。

如 HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。

1.4 具体实例

下面给出一个基于 java-jwt 生成的具体实例

java 复制代码
public static void main(String[] args) {
    String token = JWT.create().withIssuer("一灰灰blog").withExpiresAt(new Date(System.currentTimeMillis() + 86400_000))
            .withPayload(MapUtils.create("uname", "一灰", "wechat", "https://spring.hhui.top/spring-blog/imgs/info/wx.jpg", "site", "https://spring.hhui.top"))
            .sign(Algorithm.HMAC256("helloWorld"));
    System.out.println(token);
}

技术派的应用姿势

  1. 通用的jwt鉴权方案

JWT鉴权流程

一个简单的基于jwt的身份验证方案如下图

基本流程分三步:

1 用户登录成功之后,后端将生成的jwt返回给前端,然后前端将其保存在本地缓存;

2 之后前端与后端的交互时,都将jwt放在请求头中,比如可以将其放在Http的身份认证的请求头Authorization,也可以通过自定义的请求头来传递

3 后端接收到用户的请求,从请求头中获取jwt,然后进行校验,通过之后,才响应相关的接口;否则表示未登录

说明:技术派沿用session的方案,依然将jwt写入到cookie中

  1. jwt时使用姿势

接下来看一下再技术派中的实际使用场景,核心逻辑再com.github.paicoding.forum.service.user.service.help.UserSessionHelper

我们直接再之前session的基础上进行优化,这里主要借助开源项目java-jwt来实现JWT的生成管理

生成JWT的实现

我们定义了签发者、有效期,指定了签名算法,然后实体类中,携带两个信息

●s: 即之前生成的sessionId,我们借助自定义的traceId生成工具来生成唯一的会话id

●u: 用户userId

上面的实现中,有几个通用的成员属性,我们通过自定义的配置,再项目启动时进行初始化,避免后续的重复创建

自定义的jwt三个配置属性

java 复制代码
paicoding:
  jwt:
    issuer: pai_coding # 签发者
    secret: hello_world # 签名密钥
    expire: 2592000000 # jwt的有效期,默认30天

jwt校验

用户每次请求时,依然是沿用之前的session方式的校验逻辑,再Filter层,从请求头中,获取Cookie,找到对应的jwt,然后尝试根据jwt获取对应的用户信息

注意上面的实现,即便jwt校验通过了,我们也依然从redis中去查了一下,判断是否有效

为什么这么设计呢?

因为jwt本身无状态,后端完全可以将redis这一层的存储都直接干掉,纯依赖jwt的特性来完成身份鉴权,但是由于它的无状态,后端减少存储压力是一个好处,同样也是一个弊端,后端失去了token的管控权限,如果我们希望提前失效某些用户身份,则无法支持

鉴于此,我们依然保留了redis中存储jwt的方案(这是主要原因,当然技术派作为一个让大家获取更多的知识点教学相长的项目,我们会尽可能的将相关知识点给引入进来,且尽量满足大厂项目规范来实现)

  1. 实例体验

接下来我们实际体验一下,线上运行技术派的jwt,登录之后,找到一个请求,找一下cookie中的f-session

我们解析一下看下是啥

站在用户的角度,说实话你用session还是jwt,没有什么实质的感受差异,那么为什么还会有这两种不同的技术方向呢?

session与jwt的对比

|------------------------------|--------------------------|
| jwt | session |
| 前端存储,通用的校验规则,后端再获取jwt时校验是否有效 | 前端存索引,后端判断session是否有效 |
| 验签,不可篡改 | 无签名保障,安全性由后端保障 |
| 可存储非敏感信息,如用户名,头像等 | 一般不存储业务信息 |
| jwt生成时,指定了有效期,本身不支持续期以及提前失效 | 后端控制有效期,可提前失效或者自动续期 |
| 通常以请求头方式传递 | 通常以cookie方式传递 |
| 可预发csrf攻击 | session-cookie方式存在csrf风险 |

关于上面的安全风险,给一个简单的扩展说明

相关推荐
坐吃山猪3 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫3 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao4 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区5 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT6 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy6 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss7 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续8 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0448 小时前
ReAct模式解读
java·ai