登录认证(4):令牌技术:JWT令牌

如上文所说(登录认证(1):登录的基本逻辑及实现思路登录),因为 HTTP协议是无状态的协议,我们需要使用会话跟踪技术实现同一会话中不同请求之间的数据共享,但Cookie技术Session技术都有各自的使用局限,所以说在当今的企业开发中,越来越青睐令牌技术 来进行会话跟踪。

令牌技术概览

在登录认证中,令牌是用户的身份标识,是合法的身份凭证,好像十分神秘和高大上,但其本质是一个字符串。如果使用令牌技术进行会话跟踪,在浏览器发起请求,请求登录接口,如果登录成功,那么就在服务端生成一个令牌令牌就是用户的合法身份凭证,在响应数据的时候,就可以将令牌直接响应给前端。

前端程序接收到令牌之后,就需要将令牌存储起来,可以存储在cookie中,也可以存储在localStorage这样的其他存储空间。之后,在后续的每一次请求中,都需要携带令牌,服务端的统一拦截器需要校验令牌的有效性。如果令牌有效,则说明用户已经执行了登录操作,拦截器就可以放行;如果令牌无效(解析令牌报错),那么则说明用户没有执行登录操作,拦截器就需要拦截,并返回错误代码,让用户登录。整个流程如下图所示:

此时,在同一个会话的多次请求之间,我们就通过将数据存储在令牌中的方式完成了数据共享。令牌技术有很多优点,比如:支持多端,不但支持PC端,而且支持移动令牌技术也可以解决服务器集群的认证问题,因为只需要成功解析令牌,就可以证明令牌是有效的,是无需在服务器存储的令牌的安全性也非常强悍。但也是因为其强悍的性能,令牌使用起来会更加的复杂,但是这些劣势在优势面前就不值一提了。

JWT令牌

令牌的形式有很多,本文讲解功能强大、使用最广泛的JWT令牌。JWT令牌(JSON Web Token),定义了一种简洁的自包含的格式,可用于通信双方以Json数据格式安全的传输信息。

特性

简洁

JWT令牌的本质就是字符串,可以作为请求参数或者在请求头中直接传递。

自包含

JWT令牌虽然是一个字符串,但是可以根据需求,在令牌中存储自定义的数据内容,比如在登录操作中,可以在JWT令牌中存储用户相关信息。

安全

简单理解,JWT令牌就是将原本简单的Json数据进行了安全的封装,这样就可以直接在JWT令牌中进行信息传输了,并且由于数字签名的存在,这些信息是安全可靠的

组成

JWT令牌三个部分组成,每个部分之间使用.进行分隔。一个完整的JWT令牌如图所示:

第一部分:Header(头)

该部分主要是记录令牌类型令牌使用的签名算法等。例如:{"alg":"HS256", "type":"JWT"},从这个Header信息就可以看出:这是一个JWT令牌,使用了HS256签名算法

第二部分:Payload(有效载荷)

该部分主要是携带一些自定义的信息,或者一些默认的信息等,例如:{"id":"1","username":"Tom"},从这个Payload信息可以看出:这个令牌携带的数据是一个用户数据,id为1,username为Tom

第三部分:Signature(签名)

这个部分主要是令牌的签名,签名可以防止Token被篡改,可以提高令牌的安全性。其构成是将HeaderPayload两个部分,加入指定密钥(Secret),并通过指定的签名算法计算而来。正是因为数字签名,所以说JWT令牌是非常安全的,一旦令牌中的任何一个部分、任何一个字符被篡改了,整个令牌在校验时都会失效。

Base64编码

那么JWT令牌是如何将原始的Json数据,转变为字符串的呢?在生成JWT令牌的时候,对原始数据进行了Base64编码这并非是一种加密方式,只是一种编码方式)。

Base64编码:是一种基于64个可打印的字符来表示二进制数据的编码方式。所使用的64个字符分别是A到Za到z0-9一个加号(+),一个斜杠(/),加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。在有些情况下,Basae64编码可能会出现一个等号(=)。等号是一个补位的符号。

使用

想要使用JWT令牌,首先需要引入JWT对应的Maven坐标

<!-- JWT依赖-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
生成JWT令牌

在引入了JWT依赖之后,就可以使用对应的工具类Jwts提供的API来完成JWT令牌生成与校验

/*** 生成JWT令牌*/
@Test
public void testGenerateJWT() {Map<String, Object> claims = new HashMap<>();claims.put("id", 10);claims.put("username", "wzb");// 通过Jwts工具类中的builder方法构建一个JWT令牌String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "hello") // 加密算法和签名.addClaims(claims) // 添加数据:键值对.setExpiration(new Date(System.currentTimeMillis() + 12 * 3600 * 1000)) // 设置JWT令牌有效期.compact(); // 生成令牌log.info("jwt令牌是:{}", jwt);
}

这个代码主要是使用Jwts工具类提供的API生成一个JWT令牌。使用signWith方法,指定该令牌的加密算法为HS256,并且指定签名(这里的签名指定的十分粗糙,实际开发中需要指定更加复杂的签名来增加令牌安全性。);Jwt令牌中的数据都是以键值对的形式出现的,所以说可以直接把需要的数据封装为一个Map,然后使用addClaims方法,将Map中的数据封装到令牌中;然后再使用setExpiration方法,设置令牌的有效时间,此处设置的有效时间是12h;最后使用compact方法生成JWT令牌。运行这个测试方法,得到的JWT令牌

eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAsInVzZXJuYW1lIjoid3piIiwiZXhwIjoxNzM3NTk0NDgxfQ.m4BT-AR8cMCEmMJoNNL0iuyNCHylKdasuu77ETsLHcw

将该令牌拿到JWT令牌官网(JSON Web Tokens - jwt.io)解析: 

JWT令牌的最后一个部分并非Base64编码,而是签名算法计算的,所以说最后一个部分不会解析。

成功解析令牌内容,加密算法、数据等都和代码封装得相同,说明我们成功生成了一个JWT令牌

校验JWT令牌

校验(解析)JWT令牌和生成一样,同样需要使用Jwts工具类提供的API

/*** 解析JWT令牌*/
@Test
public void testParseJWT() {Claims claims = Jwts.parser().setSigningKey("hello").parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MTAsInVzZXJuYW1lIjoid3piIiwiZXhwIjoxNzM3NDI1MTg" +"5fQ.cHdgtWD-BtrnM2Xnl418CcB_6h-rB2ogGSK8aT4T0dk").getBody();log.info("JWT解析结果为:{}", claims);
}

解析JWT令牌必须要使用和生成时同样的签名,通过Jwts中的parser方法解析一个JWT令牌,必须在setSignKey方法中传递和生成时一样的签名,否则解析失败;在parseClaimsJws方法中传递需要解析的JWT令牌,即可解析其Payload部分,也就是令牌中的数据;最后通过getBody方法即可获得令牌数据的键值对对象Claims,Claims继承了Map类,可以理解为一种特殊的Map。让我们用该程序解析刚才生成的JWT令牌

成功解析令牌,说明解析令牌方法成功。JWT令牌的解析(校验)十分严格,只要有一点错误,不论哪个部分,都无法解析,即使我们只是改变了一个字符的大小写,在运行解析方法的时候都会报错,可以看出JWT令牌的可靠性。过期的JWT令牌也无法解析,令牌过期之后,令牌就会马上失效,解析就会失败。

利用JWT令牌完善登录功能

上文已经对JWT令牌做出了详细的解释与分析,JWT令牌最典型的应用就是登录,所以说让我们用JWT令牌技术来完善登录功能。具体思路前面已经分析过了,主要分为两步操作:

生成令牌

在用户登录成功之后,生成一个JWT令牌,并且把这个令牌直接返回给客户端,之后的每一个请求,都要携带这个令牌。

校验令牌

请求携带了JWT令牌,统一拦截器需要拦截请求,从请求中获取令牌,并对令牌进行解析,如果成功解析,那么就放行;如果解析失败,则返回错误代码

我们先要创建一个JwtUtils工具类,这个工具类提供生成、解析令牌的方法,以便程序使用:

package com.wzb.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;import java.util.Date;
import java.util.Map;public class JWTUtils {// 独创数字签名private static final String signKey = "hello";// 令牌过期时间private static final Long expire = 43200000L;/*** 生成的JWT令牌* @param claims JWT令牌中的数据,键值对* @return JWT令牌*/public static String generateJWT(Map<String, Object> claims) {return Jwts.builder().signWith(SignatureAlgorithm.HS256, signKey).addClaims(claims).setExpiration(new Date(System.currentTimeMillis() + expire)).compact();}/*** 解析JWT令牌* @param jwt JWT令牌* @return Claims,JWT令牌中的数据*/public static Claims parseJWT(String jwt) {return Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();}}

然后改造登录方法:

/*** 员工登录** @param emp 登录请求数据封装的Emp实体对象* @return LoginInfo员工登录信息*/
@Override
public LoginInfo login(Emp emp) {Emp empLogin = empMapper.getUserByUsernameAndPassword(emp);Map<String, Object> claims = new HashMap<>();if (empLogin != null) {Integer id = empLogin.getId();String username = empLogin.getUsername();String name = empLogin.getName();claims.put("id", id);claims.put("username", username);return new LoginInfo(id, username, name, JWTUtils.generateJWT(claims));}return null;
}

从数据库中查询到员工的信息之后,将其以键值对的方式封装到Map中,然后调用工具类中的方法生成JWT令牌之后,封装到登录信息类LoginInfo中返回。此时,就成功给客户端返回了令牌,此时再次发起登录请求: 

 

成功给客户端响应JWT令牌

总结

JWT令牌是现在越来越流行,使用越来越广泛的会话跟踪技术,可以在多端使用,并且有极强的安全性能,还可以应对服务器集群问题,是解决登录认证问题的最佳选择。现在已经有了令牌来标识用户已经登录,但是程序仍然没有对请求进行拦截,验证其是否登录,统一拦截器的部分且听下文分解。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025.1.20——二、buuctf BUU UPLOAD COURSE 1 1 文件上传

题目来源&#xff1a;buuctf BUU UPLOAD COURSE 1 1 目录 一、打开靶机&#xff0c;查看信息 二、解题思路 step 1&#xff1a;上传一句话木马.php文件康康回显 step 2&#xff1a;蚁剑连接 三、小结 一、打开靶机&#xff0c;查看信息 这里提示到了文件会被上传到./uplo…

【玩转全栈】----Django制作部门管理页面

目录 大致效果 BootStrap BootStrap简介 BootStrap配置 BootStrap使用 基本配置 部分代码解释及注意&#xff1a; 用户编辑&#xff1a; 新添数据&#xff1a; 删除数据&#xff1a; 大致效果 我先给个大致效果&#xff0c;基本融合了Django、Bootstrap、css、html等等。 基于D…

新年好(Dijkstra+dfs/全排列)

1135. 新年好 - AcWing题库 思路&#xff1a; 1.先预处理出1,a,b,c,d,e到其他点的单源最短路&#xff0c;也就是进行6次Dijkstra 2.计算以1为起点的这6个数的全排列&#xff0c;哪种排列方式所得距离最小&#xff0c;也可以使用dfs 1.Dijkstradfs #define int long longusing …

Golang之Context详解

引言 之前对context的了解比较浅薄&#xff0c;只知道它是用来传递上下文信息的对象&#xff1b; 对于Context本身的存储、类型认识比较少。 最近又正好在业务代码中发现一种用法&#xff1a;在每个协程中都会复制一份新的局部context对象&#xff0c;想探究下这种写法在性能…

AIGC浪潮下,图文内容社区数据指标体系如何构建?

文章目录 01 案例&#xff1a;以图文内容社区为例实践数据指标体构建02 4个步骤实现数据指标体系构建1. 明确业务目标&#xff0c;梳理北极星指标2. 梳理业务流程&#xff0c;明确过程指标3. 指标下钻分级&#xff0c;构建多层级数据指标体系4. 添加分析维度&#xff0c;构建完…

数据结构:二叉树

目录 一、树型结构 1、基本概念 2、重要概念 3、树的表示形式 二、二叉树 1、概念 2、两种特殊的二叉树 3、二叉树的性质 4、二叉树的存储 5、二叉树的遍历 二叉树的构建 &#xff08;1&#xff09;前序遍历 &#xff08;2&#xff09;中序遍历 &#xff08;3&am…

SpringBoot项目中的异常处理

定义错误页面 SpringBoot 默认的处理异常的机制&#xff1a;SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会像/error 的 url 发送请求。在 springBoot 中提供了一个叫 BasicExceptionController 来处理/error 请求&#xff0c;然后跳转到…

《安富莱嵌入式周报》第349期:VSCode正式支持Matlab调试,DIY录音室级麦克风,开源流体吊坠,物联网在军工领域的应用,Unicode字符压缩解压

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; 《安富莱嵌入式周报》第349期&#xff1a;VSCode正式支持Matlab调试&#xff0c;DIY录音室级麦克风…

C++priority_queue模拟实现

Cpriority_queue模拟实现 1.priority_queue基本概念2.priority_queue基本结构3.size()成员函数4.empty()成员函数5.top()成员函数6.push()成员函数7.pop()成员函数8.构造函数9.完整代码 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#…

[STM32 HAL库]串口中断编程思路

一、前言 最近在准备蓝桥杯比赛&#xff08;嵌入式赛道&#xff09;&#xff0c;研究了以下串口空闲中断DMA接收不定长的数据&#xff0c;感觉这个方法的接收效率很高&#xff0c;十分好用。方法配置都成功了&#xff0c;但是有一个点需要进行考虑&#xff0c;就是一般我们需要…

嵌入式 工程配置

本次用的STM32F4芯片系列 目录 1. 新建文件夹 2. 新建文件夹下创建 3. 打开keil5 3.1.1 点击菜单栏project 点击new project 3.1.2. 选择刚刚新建的文件夹 3.1.3.将项目文件保存到Project文件夹里 3.1.4. 将项目命名这里命名为STM32 保存 3.1.5. 保存好后会跳出选择芯…

我的图形布局 组织结构图布局

组织结构图布局,有的人也叫它树状布局,在图形中是经常用到的布局算法.形成类似如下图的图形布局方式 首先创建一个类, public class TreeLayouter {private int m_space 40;/// <summary>/// 空间间隔/// </summary>public int Space{get { return m_space; }se…

计算机网络介质访问控制全攻略:从信道划分到协议详解!!!

一、信道划分介质访问控制 介质访问控制&#xff1a;多个节点共享同一个“总线型”广播信道时&#xff0c;可能发生“信号冲突” 应该怎么控制各节点对传输介质的访问&#xff0c;才能减少冲突&#xff0c;甚至避免冲突? 时分复用(TDM) 时分复用&#xff1a;将时间分为等长的“…

sql主从同步

今天给大家介绍两种mysql的主从同步方式&#xff1a;第一种是基于binlogzhu主从同步&#xff1b;第二种就是基于gtid的主从同步方式。 首先给大家介绍一下什么是sql的主从复制。 主从复制&#xff1a; 通过将MySQL的某一台主机&#xff08;master&#xff09;的数据复制到其…

计算机组成原理——数据表示(二)

当生活的压力和困惑缠绕在身边&#xff0c;我们往往需要振奋精神&#xff0c;勇往直前。无论在何种困境中&#xff0c;我们都要保持积极的态度和坚定的信念。将悲观的情绪抛之脑后&#xff0c;展现出坚强的意志力和无尽的活力。振奋精神意味着我们要战胜自己内心的负面情绪&…

Spring Boot整合Thymeleaf、JDBC Template与MyBatis配置详解

本文将详细介绍如何在Spring Boot项目中整合Thymeleaf模板引擎、JDBC Template和MyBatis&#xff0c;涵盖YAML配置、依赖版本匹配、项目结构设计及代码示例。 一、版本兼容性说明 Spring Boot版本与Java版本对应关系 Spring Boot 2.x&#xff1a;支持Java 8、11&#xff08;推…

概率论里的特征函数,如何用卷积定理去理解

概率论里的特征函数&#xff0c;如何用卷积定理去理解_哔哩哔哩_bilibili

论文笔记(六十二)Diffusion Reward Learning Rewards via Conditional Video Diffusion

Diffusion Reward Learning Rewards via Conditional Video Diffusion 文章概括摘要1 引言2 相关工作3 前言4 方法4.1 基于扩散模型的专家视频建模4.2 条件熵作为奖励4.3 训练细节 5 实验5.1 实验设置5.2 主要结果5.3 零样本奖励泛化5.4 真实机器人评估5.5 消融研究 6 结论 文章…

HashMap用法

一、构造方法 构造方法有4个。 1、手动声明初始容量及负载因子的构造函数。初容容量的最大值不能超过MAXIMUM_CAPACITY 2、手动声明初始容量的构造函数&#xff0c;负载因子是默认大小。 默认的负载因子是0.75 3、无参的构造函数&#xff0c;会指定默认的负载因子。容量是默…

Java基础 (一)

基础概念及运算符、判断、循环 基础概念 关键字 数据类型 分为两种 基本数据类型 标识符 运算符 运算符 算术运算符 隐式转换 小 ------>>> 大 强制转换 字符串 拼接符号 字符 运算 自增自减运算符 ii赋值运算符 赋值运算符 包括 强制转换 关系运算符 逻辑运算符 …