掌握JWT:解密身份验证和授权的关键技术

JSON Web Token

  • 1、什么是JWT
  • 2、JWT解决了什么问题
  • 3、早期的SSO认证
  • 4、JWT认证
  • 5、JWT优势
  • 6、JWT结构
          • Header 标头
          • Payload 负载
    • Signature 签名
  • 7、代码实现
    • 添加依赖
    • 生成Token
    • 认证token
  • 8、工具类
  • 9、JWT整合Web
  • 10、拦截器校验
  • 11、网关路由校验
  • 12、解决多用户登录的问题
  • 13、客户端保存/携带token
  • 14、抽取ajax工具类
  • 15、a标签跳转如何传递token

1、什么是JWT

官方文档解释:JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

官网地址: https://jwt.io/introduction/

通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,客户端请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据。

2、JWT解决了什么问题

  • 授权:这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛;
  • 信息交换:JWT是在各个服务之间安全传输信息的好方法。因为JWT可以签名,例如,使用公钥/私钥是以对儿 - 可以确定请求方是合法的。此外,由于使用标头和有效负载计算签名,还可以验证内容是否未被篡改。

3、早期的SSO认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

在这里插入图片描述

4、JWT认证

在这里插入图片描述

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议)。

  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同aaaa.bbb.cc的字符串。 token head.payload.singurater。

  • 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。

  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER。

  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

  • 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

5、JWT优势

JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

  • 简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快。

  • 自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库。

  • 自校验

对token可以自己校验是否过期。

6、JWT结构

令牌组成

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

​ 标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法。它会使用 Base64 对header做编码,组成而来JWT结构的第一部分。
Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

{"alg": "HS256", # 签名算法"typ": "JWT" # 类型
}
Payload 负载

​ 这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。同样的,它也会使用 Base64 编码组成 JWT 结构的第二部分。

{"iss": "demo JWT","iat": 1342513302,"exp": 1342513302,"name": "admin","sub": "dev"
}

Signature 签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

三个部分通过.连接在一起就是我们的 JWT 了

签名的目的:

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息安全性:

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?
是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。
因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

在这里插入图片描述

7、代码实现

添加依赖

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

生成Token

	@Testvoid testCreateToken() {// 1.设置超时时间Calendar calendar = Calendar.getInstance();calendar.add(Calendar.SECOND,30); // 超时时间是30s// 2.创建JWTbuilderJWTCreator.Builder builder = JWT.create();// 3.设置头,负载,签名String token = builder
//				.withHeader(map) 设置头信息,可以不设置有默认值.withClaim("name", "admin").withClaim("id", 10) // 设置用户自定义属性.withExpiresAt(calendar.getTime()) // 设置令牌超时时间.sign(Algorithm.HMAC256("dalaoshi"));// 设置用户签名// 4.输出结果System.out.println(token);}

认证token

     		String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJpZCI6MTAsImV4cCI6MTU5OTQwNTQ2NH0.7YFYieOC-ChS32He7DqyVtECCvM4nFWmb7hKLiPAIXY\n";// 1.根据用户签签名获取JTW校验器JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("dalaoshi")).build();// 2.验证tokenDecodedJWT verify = jwtVerifier.verify(token);// 3.获取token的数据System.out.println(verify.getClaim("name").asString()); // 字符串使用asString()System.out.println(verify.getClaim("id").asInt()); // int使用asIntSystem.out.println(verify.getExpiresAt()); // 获取过期时间

认证常见的异常
在这里插入图片描述

- SignatureVerificationException:		签名不一致异常
- TokenExpiredException:    			令牌过期异常
- AlgorithmMismatchException:			算法不匹配异常
- InvalidClaimException:				失效的payload异常

8、工具类

public class JWTUtils {private static String sign = "dalaoshi";public static String createToken(Map<String, String> map) {// 1.设置超时时间Calendar calendar = Calendar.getInstance();calendar.add(Calendar.DATE, 7); // 7天// 2.创建JWTbuilderJWTCreator.Builder builder = JWT.create();// 设置负载数据Set<Map.Entry<String, String>> entries = map.entrySet();for (Map.Entry<String, String> entrie : entries) {builder.withClaim(entrie.getKey(), entrie.getValue());}// 3.设置签名,过期时间String token = builder.withExpiresAt(calendar.getTime()) // 设置令牌超时时间.sign(getSignature());// 设置用户签名// 4.返回return token;}// 获取起签名public static Algorithm getSignature() {return Algorithm.HMAC256(sign);}// 校验public static DecodedJWT require(String token) {return JWT.require(getSignature()).build().verify(token);}// 获取token中的数据public static Claim getPayload(String token, String key) {return require(token).getClaim(key);}
}

9、JWT整合Web

    @Autowiredprivate IUserService userService;@RequestMapping("/login")public ResultEntity login(String username,String password){ResultEntity resultEntity = userService.login(username, password);if(ResultEntity.SUCEESS.equals(resultEntity.getStatus())){Map<String,String> map = new HashMap<>();map.put("id","10");map.put("username",username);String token = JWTUtils.createToken(map);return ResultEntity.success(token);}else{return ResultEntity.error("登录失败");}}@RequestMapping("/require")public ResultEntity require(String token){try {DecodedJWT require = JWTUtils.require(token);return ResultEntity.response(require);}catch (TokenExpiredException e){return ResultEntity.error("token过期");}catch (SignatureVerificationException e){return ResultEntity.error("用户签名不一致");} catch (InvalidClaimException e){return ResultEntity.error("payload数据有误");}catch (Exception e){return ResultEntity.error("校验失败");}}@RequestMapping(value = "/getPayLoad")public ResultEntity getPayLoad(String token){DecodedJWT decodedJWT = JWTUtils.require(token);Map<String, Claim> claims = decodedJWT.getClaims();Map<String,String> map = new HashMap<>();Set<Map.Entry<String, Claim>> entries = claims.entrySet();for (Map.Entry<String, Claim> entrie:entries) {map.put(entrie.getKey(),entrie.getValue().asString());}return ResultEntity.success(map);}

10、拦截器校验

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取tokenString token = request.getHeader("token");Map<String,Object> map = new HashMap<>();try {// 2.校验JWTUtils.verify(token);return true;}catch (TokenExpiredException e){return ResultEntity.error("token过期");}catch (SignatureVerificationException e){return ResultEntity.error("用户签名不一致");} catch (InvalidClaimException e){return ResultEntity.error("payload数据有误");}catch (Exception e){return ResultEntity.error("校验失败");}// 3.校验失败响应数据String json = new ObjectMapper().writeValueAsString(map);response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);return false;
}

11、网关路由校验

@Component
public class SSOFilter extends ZuulFilter{@Autowiredprivate ISSOService ssoService;@Overridepublic String filterType() {return FilterConstants.PRE_TYPE;}@Overridepublic int filterOrder() {return FilterConstants.PRE_DECORATION_FILTER_ORDER-1;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() throws ZuulException {RequestContext requestContext = RequestContext.getCurrentContext();HttpServletRequest request = requestContext.getRequest();StringBuffer requestURL = request.getRequestURL();System.out.println(requestURL);// 1.该服务是否需要验证if("http://localhost/shop-back/user/getUserPage".equals(requestURL.toString())){String token = request.getHeader("token");// 2.验证服务ResultEntity resultEntity = ssoService.require(token);System.out.println(resultEntity);if(!ResultEntity.SUCEESS.equals(resultEntity.getStatus())){requestContext.setSendZuulResponse(false); // 不能往下执行了HttpServletResponse response = requestContext.getResponse();response.setContentType("application/json;charset=utf-8"); // 设置响应数据类型requestContext.setResponseBody(JSON.toJSONString(ResultEntity.error("校验未通过"))); // 设置响应数据}}return null;}
}

12、解决多用户登录的问题

如果一个用户登录在多个设备登录,就会出现一个用户多个token在多个设备上同时登录。如果要解决这个问题就要判断用户操作的token是否是最新的,只有是最新的token才能认证成功。

// 伪代码// login
public String login(String name,String password){// 1.查询数据库认证// 2.生成tokenString token = "";// 3.把用户最新的token放入到reids中redisTemp.set(username,token); // username作为key,多次登录key会被覆盖
}// 路由校验// 1.获取用户token
// 2.根据用户名查询用户最新的token
// 3.对比两个token是否一致,如果不一致就说明用户进行了第二次登陆,就不让认证通过。

13、客户端保存/携带token

// 登录获取token,保存到本地  
function login(){var username ="admin";var password ="123";var param = new Object();param.username=username;param.password=password;$.post("http://localhost/shop-sso/sso/login",param,function (data) {if(data.status ="success"){// 获取tokenvar token = data.data;// 保存toke到客户端localStorage.setItem("login-token",token);}},"JSON");}// 发送请求是把token放到请求头中保存function sendRequest(){$.ajax({url: "http://localhost/shop-sso/addXxxxx",type: "post",dataType: 'json',beforeSend: function (XMLHttpRequest) {// 获取本地储存的token,添加到请求头中XMLHttpRequest.setRequestHeader("Authorization", localStorage.getItem("login-token"));},success: function (result) {}});}

为什么要把token放在请求头中的Authorization中?
a)保存在请求头中方便和其他参数区分
b)保存在请求头中可以解决跨域的问题,比如cookie是存在跨域的问题
c)Authorization header就是为用户认证而生的。
d)解决XSS和XSRF问题

14、抽取ajax工具类

window.utils={ajax:function(param){$.ajax({url: param,type: "post",dataType: 'json',data:param.data,beforeSend: function (XMLHttpRequest) {XMLHttpRequest.setRequestHeader("Authorization", localStorage.getItem("login-token"));},success: function (result) {param.success(result);}});}
}// 调用
utils.ajax({url:"http://localhost/shop-sso/sso/login",data:param,success:function(data){if(data.status ="success"){// 获取tokenvar token = data.data;// 保存toke到客户端localStorage.setItem("login-token",token);}}
})

15、a标签跳转如何传递token

token只针对api设计,和原生标签的跳转没有直接的关系。如果请求跳转可以在url后面携带token。

后记
👉👉💕💕美好的一天,到此结束,下次继续努力!欲知后续,请看下回分解,写作不易,感谢大家的支持!! 🌹🌹🌹

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

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

相关文章

HTTP content-type内容类型的常见格式

本专栏是汇集了一些HTML常常被遗忘的知识&#xff0c;这里算是温故而知新&#xff0c;往往这些零碎的知识点&#xff0c;在你开发中能起到炸惊效果。我们每个人都没有过目不忘&#xff0c;过久不忘的本事&#xff0c;就让这一点点知识慢慢渗透你的脑海。 本专栏的风格是力求简洁…

C# ASP.NET 实验室 检验中心 医疗LIS源码

LIS系统能够自动处理大量的医学数据&#xff0c;包括样本采集、样本处理、检测分析、报告生成等。它能够快速、准确地进行化验检测&#xff0c;提高医院的运营效率。LIS系统还提供了丰富的数据分析功能&#xff0c;能够对医院化验室的业务流程进行全面、细致的监控。 LIS系统优…

windows搭建MySQL 8.25主从配置

1.本次搭建的版本 mysql-8.0.25-win-x64 2.在解压完成后的文件内并没有对应的my.ini的配置文件这个my.ini是需要的主配置文件需要自行创建。 注&#xff1a;安装路径及数据存放路径需根据实际安装情况进行修改&#xff08;其它配置信息可结合实际情况进行修改&#xff09; 3.在…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Progress进度条组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Progress进度条组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Progress组件 进度条也是UI开发最常用的组件之一&#xff0c;进度条组件…

python区块链简单模拟【05】

新增内容&#xff1a;构建去中心化网络 import socket #套接字&#xff0c;利用三元组【ip地址&#xff0c;协议&#xff0c;端口】可以进行网络间通信 import threading #线程 import pickle# 定义一个全局列表保存所有节点 NODE_LIST []class Node(threading.Thread…

【Spring实战】05 CommandLineRunner

文章目录 1. 简介2. 用法1&#xff09;单个 CommandLineRunner2&#xff09;多个 CommandLineRunner 3. 优点4. 缺点总结 CommandLineRunner 是 Spring Boot 提供的一个接口&#xff0c;用于在 Spring Boot 应用程序启动后执行一些任务。通过实现 CommandLineRunner 接口&#…

目标追踪:使用ByteTrack进行目标检测和跟踪

BYTE算法是一种简单而有效的关联方法&#xff0c;通过关联几乎每个检测框而不仅仅是高分的检测框来跟踪对象。这篇博客的目标是介绍ByteTrack以及多目标跟踪&#xff08;MOT&#xff09;的技术。我们还将介绍在样本视频上使用ByteTrack跟踪运行YOLOv8目标检测。 多目标跟踪&…

【数据结构和算法】找到最高海拔

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 前缀和的解题模板 2.1.1 最长递增子序列长度 2.1.2 寻找数组中第 k 大的元素 2.1.3 最长公共子序列…

2015年第四届数学建模国际赛小美赛C题科学能解决恐怖主义吗解题全过程文档及程序

2015年第四届数学建模国际赛小美赛 C题 科学能解决恐怖主义吗 原题再现&#xff1a; 为什么人们转向恐怖主义&#xff0c;特别是自杀性恐怖主义&#xff1f;主要原因是什么&#xff1f;这通常是大问题和小问题的结合&#xff0c;或者是一些人所说的“推拉”因素。更大的问题包…

Azure Machine Learning - Azure OpenAI GPT 3.5 Turbo 微调教程

本教程将引导你在Azure平台完成对 gpt-35-turbo-0613 模型的微调。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&…

【力扣题解】P226-翻转二叉树-Java题解

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【力扣题解】 文章目录 【力扣题解】P226-翻转二叉树-Java题解&#x1f30f;题目描述&#x1f4a1;题解&#x1f30f;总结…

Vue-Pinina基本教程

前言 官网地址&#xff1a;Pinia | The intuitive store for Vue.js (vuejs.org) 看以下内容&#xff0c;需要有vuex的基础&#xff0c;下面很多概念会直接省略&#xff0c;比如state、actions、getters用处含义等 1、什么是Pinina Pinia 是 Vue 的存储库&#xff0c;它允许您跨…