JWT基础教程

JWT

目标

JWT 实现无状态 Web 服务【掌握】

nimbus-jose-jwt 库【重点】

token续期【重点】

一、 JWT 实现无状态 Web 服务

1、什么是有状态

有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,无法进行水平扩展
  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

2、什么是无状态

服务器不需要记录客户端的状态信息,即:

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

带来的好处是什么呢?

  • 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩
  • 减小服务端存储压力

3、如何实现无状态

无状态登录的流程:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务的对token进行解密,判断是否有效。

流程图:

​ 客户端请求登录,登录之后颁发凭证

在这里插入图片描述

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用:JWT + RSA非对称加密

4、JWT简介

JWT全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

JWT包含三部分数据:

  • Header:头部,通常头部有两部分信息:

    • 声明类型,这里是JWT 自描述信息

    我们会对头部进行base64编码,得到第一部分数据 base64编码和解码的

  • Payload:载荷,就是有效数据,一般包含下面信息:

    • 用户身份信息(注意,这里因为采用base64编码,可解码是可逆的,因此不要存放敏感信息)
    • 注册声明:如token的签发时间,过期时间,签发人等 这部分内容 好比身份证的信息

    这部分也会采用base64编码,得到第二部分数据

  • Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上密钥(secret)(不要泄漏,最好周期性更换),通过加密算法(不可逆的)生成一个签名。用于验证整个数据完整和可靠性。

生成的数据格式:

在这里插入图片描述

可以看到分为3段,每段就是上面的一部分数据

5、JWT交互流程

步骤翻译:

  • 1、用户登录
  • 2、服务的认证,通过后生成jwt
  • 3、将生成的jwt返回给浏览器
  • 4、用户每次请求携带jwt
  • 5、服务端利用公钥解读jwt签名,判断签名有效后,从Payload中获取用户信息
  • 6、处理请求,返回响应结果

二、nimbus-jose-jwt 库

1、进入依赖

nimbus-jose-jwt、jose4j、java-jwt 是几个 Java 中常见的操作 JWT 的库

nimbus-jose-jwt 官网:https://connect2id.com/products/nimbus-jose-jwt

所需坐标

    <dependency>
        <groupId>com.nimbusds</groupId>
        <artifactId>nimbus-jose-jwt</artifactId>
        <version>9.11.1</version>
    </dependency>

2、核心 API

2.1、加密过程

  • 在 nimbus-jose-jwt 中,使用 Header 类代表 JWT 的头部,不过,Header 类是一个抽象类,我们使用的是它的子类 JWSHeader

    创建头部对象:

     @Test
     public void createToken(){
         //创建头部对象
         JWSHeader jwsHeader =
                 new JWSHeader.Builder(JWSAlgorithm.HS256) // 加密算法
                         .type(JOSEObjectType.JWT) // 静态常量
                         .build();
         System.out.println(jwsHeader);
     }
    

    你可以通过 .toBase64URL() 方法求得头部信息的 Base64 形式(这也是 JWT 中的实际头部信息):

  • 使用 Payload 类的代表 JWT 的荷载部分

    创建荷载部对象:

        @Test
        public void createToken(){
            //创建头部对象
            JWSHeader jwsHeader =
                    new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                            .type(JOSEObjectType.JWT) // 静态常量
                            .build();
            System.out.println(jwsHeader);
    
            //创建载荷
            Payload payload = new Payload("hello world");
            System.out.println(payload);
        }
    

    你可以通过 .toBase64URL() 方法求得荷载部信息的 Base64 形式(这也是 JWT 中的实际荷载部信息):

  • 签名部分

    ​ 签名部分没有专门的类表示,签名部分并非你自己创建出来的,而是靠 头部 + 荷载部 + 加密算法 算出来的

    ​ nimbus-jose-jwt 专门提供了一个签名器 JWSSigner ,用来参与到签名过程中。密钥就是在创建签名器的时候指定的:

    JWSSigner jwsSigner = new MACSigner("密钥");  //MACSigner()中要指定一个密钥
    

    最终,整个 JWT 由一个 JWSObject 对象表示:

    JWSObject jwsObject = new JWSObject(jwsHeader, payload);
    // 进行签名(根据前两部分生成第三部分)
    jwsObject.sign(jwsSigner);
    

    我们最终要的是 JWT 字符串,而不是对象,这里接着对代表 JWT 的 JWSObject 对象调用 .serialize() 方法即可:

    String token = jwsObject.serialize();
    

    完整示例:

      @Test
        public void createToken() throws JOSEException {
    
            //创建头部对象
            JWSHeader jwsHeader =
                    new JWSHeader.Builder(JWSAlgorithm.HS256)       // 加密算法
                            .type(JOSEObjectType.JWT) // 静态常量
                            .build();
            //创建载荷
            Payload payload = new Payload("hello world");
    
            //创建签名器
            JWSSigner jwsSigner = new MACSigner("woniu");//woniu为密钥
            //创建签名
            JWSObject jwsObject = new JWSObject(jwsHeader, payload);// 头部+载荷
            jwsObject.sign(jwsSigner);//再+签名部分
    
            //生成token字符串
            String token = jwsObject.serialize();
            System.out.println(token);
        }
    

如果出现:com.nimbusds.jose.KeyLengthException: The secret length must be at least 256 bits异常,是因为密钥的长度不够增加密钥长度即可

2.2、 解密

反向的解密和验证过程核心 API 就 2 个:JWSObject 的静态方法 parse 方法和验证其 JWSVerifier 对象。

如果你想直接验证 JWSObject 对象的合法性,你需要创建一个 JWSVerifier 对象。

//创建验证器
JWSVerifier jwsVerifier = new MACVerifier("密钥");//密钥要和加密时的相同

然后直接调用 jwsObject 对象的 verify 方法:

if (!jwsObject.verify(jwsVerifier)) {
    throw new RuntimeException("token 签名不合法!");
}

三、token续期

在实际的开发中,token不可能一直有效,比如30分钟内一次都没有进行操作,则认证过期,需要重新登录,如果一直在进行请求访问则token一直有效,直到上一次访问距离下一次访问的时间超过了30分钟,则认证过期。

springsecurity整合JWT:

@Component
public class JWTfilter extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired(required = false)
    private SecurityLoginService securityLoginService;

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        //功能点1:在请求头拿到jwt
        String jwt = httpServletRequest.getHeader("jwt");
        if (jwt == null) {
            //放给security 其他过滤器,该方法不做处理
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        // 功能点2:jwt不合法
        if (!JWTUtil.decode(jwt)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        //功能点3 获取jwt的用户信息
        Map payLoad = JWTUtil.getPayload(jwt);
        String username = (String) payLoad.get("username");

        //拿到redis的jwt
        String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);

        //判断redis是否有该jwt
        if (redisJWT == null) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        if (!jwt.equals(redisJWT)) {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }

        //给redis 的jwt续期
        redisTemplate.opsForValue().set("jwt:" + username, jwt, 30,
                TimeUnit.MINUTES);

        //获取用户名,密码,权限
        UserDetails userDetails = securityLoginService.loadUserByUsername(username);

        // 获取用户信息 生成security容器凭证
        UsernamePasswordAuthenticationToken upa =
                new UsernamePasswordAuthenticationToken(userDetails.getUsername()
                        , userDetails.getPassword(), userDetails.getAuthorities());

        //放入凭证
        SecurityContextHolder.getContext().setAuthentication(upa);

        // 本方法共功能执行完了,交给下一个过滤器
        filterChain.doFilter(httpServletRequest, httpServletResponse);

    }
}
   //前后端项目中要禁用掉session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
在securityConfig 类注入
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/4524.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

注意力汇聚 笔记

10.2. 注意力汇聚&#xff1a;Nadaraya-Watson 核回归 — 动手学深度学习 2.0.0 documentation 要是有错请指正 想了半天这个玩意才搞懂一点点, 1.设计出一个函数 给予真实的连续x 得出真实连续的y 2.随机生成用于测试的一批不连续的 离散的x 并将测试x放进函数中并加入一…

IO进程线程-标准IO(结)

目录 1.思维导图 2.笔记 3.作业 3.1题 3.2题 1.思维导图 2.笔记 有道云笔记 3.作业 3.1题 计算文件行数 // 使用fputs fgets // 要求拷贝一个文件&#xff0c;例如将1.c中的内容拷贝到2.c中 // 要求计算一个文件的大小。#include <stdio.h> #include <string.…

python条件语句与循环语句

目录 一、条件语句 1.1if 二、循环语句 2.1while 2.2for循环 2.3break和continue 三、test和总结 一、条件语句 1.1if Python条件语句是通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 Python程序语言指定&#xff1a; 任…

TCC真没这么简单,一文讲透|分布式事务系列(三)

本文从两个场景说起&#xff0c;详细描述了TCC的详细过程&#xff0c;以及对比2PC有什么区别&#xff0c;适用什么样的场景。点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达在面试前复习 TCC 的时候你是不是这样做的&#xff1a;百度TCC关…

Java基础 -- 关键字Static和Final

Java基础 -- 关键字Static和Final1. Static1.1 修饰成员变量1.2 修饰方法1.3 代码块1.3.1 代码块011.3.2 代码块022. Final2.1 初始化2.2 思考3. 类属性值的Null和非空判断4. Awakening1. Static java static关键字可以用在变量、方法、代码块和嵌套类上 1.静态变量 2.静态方法…

docker-compose部署rabbitmq集群

1、集群分类 RabbitMQ的是基于Erlang语言编写&#xff0c;而Erlang又是一个面向并发的语言&#xff0c;天然支持集群模式。 RabbitMQ的集群以下分类&#xff1a; 标准集群&#xff1a;是一种分布式集群&#xff0c;将队列分散到集群的各个节点&#xff0c;从而提高整个集群的并…

解决 Git 错误 error: failed to push some refs to ‘https://*****.git‘

1. 错误描述 当在 git 上创建好仓库后在上传时出现 ! [rejected] main -> main (fetch first)&#xff0c;error: failed to push some refs to *****。 2. 产生错误的原因 我们在创建仓库的时候&#xff0c;都会勾选 添加 README 文件&#xff0c;这个操作自动创建了一个 …

春分-面试

青岛 zc&#xff1a; 1.String的类型 string、stringbuilder 、stringbuffer&#xff1f; String不可变、另外两个可变、StringBuilder线程不安全、但是效率高、并且String不能被继承。 JVM是C写的编译后的机器码&#xff0c; 2.集合类的用法&#xff0c;还问了键值对。key如果…

LeetCode:242. 有效的字母异位词

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; &#x1f33b;算法&#xff0c;不如说它是一种思考方式&#x1f340;算法专栏&#xff1a; &#x1f449;&#x1f3fb;123 文章目录一、&#x1f331;[242. 有效的字母异位词](https://leetcode.cn/problems/valid-…

【Autoware规控】Lattice规划节点

文章目录1. Lattice规划介绍2. 相关代码1. Lattice规划介绍 Lattice Planner 是一种基于栅格地图的规划算法&#xff0c;通过搜索和优化实现路径规划的目的。Lattice Planner 的核心思想是将路径规划问题转化为一系列离散化的决策问题&#xff0c;通过搜索和优化得到最优路径&…

CentOS挂载U盘拷贝文件

1.登录linux操作系统&#xff0c;将U盘插入主机 2.新建一个目录将U盘挂载到该目录 使用命令: mkdir /mnt/usb 3.查看可用的挂载点 使用命令&#xff1a; fdisk -l 4. 将U盘挂载到刚才建立的目录下 使用命令: mount /dev/sdb4 /mnt/usb 5.查看U盘识别情况 使用命令 &#x…

【基础算法】1-2:归并排序

归并排序 OVERVIEW归并排序1.归并排序&#xff08;1&#xff09;基本思想&#xff08;2&#xff09;归并排序特性2.归并排序模板3.归并排序练习&#xff08;1&#xff09;AcWing787.归并排序&#xff08;2&#xff09;AcWing788.逆序对的数量1.归并排序 &#xff08;1&#xf…

使用c++超详细解释数据结构中的顺序栈和链栈

在C中&#xff0c;栈&#xff08;Stack&#xff09;是一种数据结构&#xff0c;它可以用来存储数据&#xff0c;并支持两种基本操作&#xff1a;压入&#xff08;Push&#xff09;和弹出&#xff08;Pop&#xff09;。栈的特点是后进先出&#xff08;Last In First Out&#xf…

大模型多模态Chatgpt+自动驾驶控制器设计方案

/导读/ 最近的科技圈&#xff0c;大家都被微软推出的ChatGPT刷屏&#xff0c;作为工智能公司OpenAI于2022年11月推出的聊天机器人&#xff0c;其能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;甚至能完成撰写邮件、视频脚本、文…

入行芯片设计选模拟IC还是数字IC?一文为你讲解清楚

数字IC设计与模拟IC设计有什么区别&#xff0c;很多入行的新人都不知道该怎么选择&#xff1f; 数字IC设计和模拟IC设计&#xff0c;其实是不同的两个方向。 喜欢挑战&#xff0c;喜欢分析电脑屏幕上可见的MOS管组成的具体电路的&#xff0c;可以选择模拟IC设计。不排斥编程序&…

树莓派云浇水--上层搭建自研版 :P

这个是个人的实际需求。如果出差&#xff0c;家中个别宝贝植物如何看护&#xff1f; 翻文章的话&#xff0c;经常看到的一套是什么检测土壤湿度&#xff0c;判断完数据后浇水的。个人觉得&#xff0c;植物浇水嘛&#xff0c;大部分的普通植物&#xff0c;看到它叶子耷拉了&am…

完全二叉树的4种遍历方式

一张二叉树的图 1&#xff0c;二叉树的特点 每个点p的左儿子是p*2,右儿子是p*21&#xff0c;可以分别表示为p<<1与p<<1|1节点的序号是从左到右&#xff0c;从上到下增加的每个点至多2个儿子&#xff08;屁话&#xff08;bushi&#xff09;&#xff09; 2&#xff…

《Spring系列》第11章 别名机制

前言 在 spring 容器中&#xff0c;允许通过名称或别名来获取 bean &#xff0c;这个能力来自于顶层接口 AliasRegistry&#xff0c;分析类下属的关系图&#xff0c;可以看到&#xff0c;几乎所有主要容器都直接或间接的实现了 AliasRegistry 接口。 AliasRegistry 的结构非…

UART、RS232 、RS485 区别

UART 、RS232 、RS485 区别 UART、RS232、RS485这些物理层的串口通信&#xff0c;它们都是在同一时间发送一位。 RS232、RS485只是串口通讯的变种&#xff0c;理解了UART串口通讯&#xff0c;那么RS232和RS485也就很好理解了。 了解今天的内容你可能还需要补充下“电平标准”…

Springboot整合rabbitmq并实现消息可靠性和持久性

Springboot整合rabbitmq并实现消息可靠性和持久性1. 环境准备2. 创建 Spring Boot 项目3. 配置 RabbitMQ4. 实现消息的可靠性和持久性4.1 发送可靠的消息4.2 接收可靠的消息5. 运行应用程序6. 总结Spring Boot 是一种快速构建应用程序的框架&#xff0c;而 RabbitMQ 是一种消息…
最新文章