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

news2025/3/18 17:06:06

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

	@Test
	void testCreateToken() {

		// 1.设置超时时间
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.SECOND,30); // 超时时间是30s


		// 2.创建JWTbuilder
		JWTCreator.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.验证token
        DecodedJWT verify = jwtVerifier.verify(token);

        // 3.获取token的数据
        System.out.println(verify.getClaim("name").asString()); // 字符串使用asString()
        System.out.println(verify.getClaim("id").asInt()); // int使用asInt
        System.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.创建JWTbuilder
        JWTCreator.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

    @Autowired
    private 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.获取token
  String 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{

    @Autowired
    private ISSOService ssoService;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER-1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public 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.生成token
    String 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"){

                    // 获取token
                    var 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"){
            // 获取token
            var token = data.data;

            // 保存toke到客户端
            localStorage.setItem("login-token",token);
        }
    }
})

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从流星雨启程:Python和Pygame下载与安装全过程

文章目录 一、前言二、下载安装过程1.官网下载安装包2.安装python过程第一步第二步第三步第四步第五步安装完成 3.简单测试Python3.1 检查 Python 版本号3.2 打开 Python 解释器3.3 输入你的第一个代码3.4 运行 Python 脚本 4.安装Pygame4.1 cmd命令安装Pygame4.2 pip升级4.3 安…

论文阅读——X-Decoder

Generalized Decoding for Pixel, Image, and Language Towards a Generalized Multi-Modal Foundation Model 1、概述 X-Decoder没有为视觉和VL任务开发统一的接口&#xff0c;而是建立了一个通用的解码范式&#xff0c;该范式可以通过采用共同的&#xff08;例如语义&#…

12月25日作业

串口发送控制命令&#xff0c;实现一些外设LED 风扇 uart4.c #include "uart4.h"void uart4_config() {//1.使能GPIOB\GPIOG\UART4外设时钟RCC->MP_AHB4ENSETR | (0x1 << 1);RCC->MP_AHB4ENSETR | (0x1 << 6);RCC->MP_APB1ENSETR | (0x1 <…

博易大师智星系统外盘资管系统的功能介绍!

1. 市场行情数据接收和显示&#xff1a;软件需要接收实时的市场行情数据&#xff0c;并将其以图形或数字的形式显示出来&#xff0c;包括价格、成交量、成交额等信息。 2. 交易操作界面&#xff1a;软件需要提供一个交易操作界面&#xff0c;供用户进行交易操作&#xff0c;包括…

HarmonyOS共享包HAR

共享包概述 OpenHarmony提供了两种共享包&#xff0c;HAR&#xff08;Harmony Archive&#xff09;静态共享包&#xff0c;和HSP&#xff08;Harmony Shared Package&#xff09;动态共享包。 HAR与HSP都是为了实现代码和资源的共享&#xff0c;都可以包含代码、C库、资源和配…

基于深度学习的非合作双基地雷达目标检测方法

源自&#xff1a;信号处理 作者&#xff1a;钟宁, 鲍庆龙, 陈健, 戴华骅 “人工智能技术与咨询” 发布 摘 要 非合作双基地雷达由于接收的目标信号能量不强且回波脉冲间相位同步困难&#xff0c;在目标检测时无法进行相参处理从而带来弱小目标检测困难的问题。为解决这一问…

【操作系统】探究进程奥秘:显示进程列表的解密与实战

​&#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;Linux专栏&#xff1a;《探秘Linux | 操作系统解密》⏰诗赋清音&#xff1a;月悬苍穹泛清辉&#xff0c;梦随星河徜徉辉。情牵天际云千层&#xff0c;志立乘风意自飞。 ​ 目录 &a…

构建外卖系统:从技术到实战

在当今高度数字化的社会中&#xff0c;外卖系统的开发变得愈发重要。本文将从技术角度出发&#xff0c;带领读者一步步构建一个基础的外卖系统&#xff0c;并涵盖关键技术和实际代码。 1. 技术选型 1.1 后端开发 选择Node.js和Express框架进行后端开发&#xff0c;搭建一个灵…

Linuxapache安装

Apache 介绍 Apache HTTP Server&#xff08;简称Apache&#xff09;是Apache软件基金会的一个开放源码的网页服务器&#xff0c;Apache是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上&#xff0c;由于其跨平台和安全性被广泛使用&#xff0…

AIGC专栏1——Pytorch搭建DDPM实现图片生成

AIGC专栏1——Pytorch搭建DDPM实现图片生成 学习前言源码下载地址网络构建一、什么是Diffusion1、加噪过程2、去噪过程 二、DDPM网络的构建&#xff08;Unet网络的构建&#xff09;三、Diffusion的训练思路 利用DDPM生成图片一、数据集的准备二、数据集的处理三、模型训练 学习…

iOS设备信息详解

文章目录 ID 体系iOS设备信息详解IDFA介绍特点IDFA新政前世今生获取方式 IDFV介绍获取方式 UUID介绍特点获取方式 UDID介绍获取方式 OpenUDID介绍 Bundle ID介绍分类其他 IP地址介绍获取方式 MAC地址介绍获取方式正常获取MAC地址获取对应Wi-Fi的MAC地址 系统版本获取方式 设备型…

Java基于TCP网络编程的群聊功能

服务端 import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List;public class Server2 {public static List<Socket> onlineList new ArrayList<>();public static void main(String[] args) throws Except…

在做题中学习:三数之和

15. 三数之和 - 力扣&#xff08;LeetCode&#xff09;15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 解释&#xff1a;不能重复也就是说不能和前一个三元组的元素完全相同 思路&#xff1a;通过做 两数之和那道题 可以想到&#xff1a; 1.先排序 2.双指针法 3.固定…

分布式核心技术之分布式锁

文章目录 为什么要使用分布锁&#xff1f;分布式锁的三种实现方法基于数据库实现分布式锁基于缓存实现分布式锁基于 ZooKeeper 实现分布式锁知识扩展&#xff1a;如何解决分布式锁的羊群效应问题&#xff1f; 三种实现方式对比 分布式互斥&#xff0c;领悟了其“有你没我&#…

解决 Solidworks2021 报错(-15,10032,0)错误记录

Solidworks2021 报错"-15,10032,0"错误记录 如图所示解决方案步骤1步骤2 个人问题我的没法添加白名单&#xff0c;要是有能解决的大神给个解决方式感激不尽&#xff01;&#xff01; 如图所示 解决方案 步骤1 该问题的解决方式仅对个人有效&#xff0c;不一定通用&…

非对称加密与对称加密的区别是什么?

在数据通信中&#xff0c;加密技术是防止数据被未授权的人访问的关键措施之一。而对称加密和非对称加密是两种最常见的加密技术&#xff0c;它们被广泛应用于数据安全领域&#xff0c;并且可以组合起来以达到更好的加密效果。本文将探讨这两种技术的区别&#xff0c;以及它们在…

C#示例(一):飞行棋游戏

1、先看一下实现效果 输入连个玩家的姓名 两个玩家分别用字母A和字母B表示 按下任意键开始掷骰子、根据骰子走对应的步数… 2、绘制游戏头 /// <summary>/// 画游戏头/// </summary>public static void GameShow(){Console.ForegroundColor ConsoleColor.Blu…

CEEMDAN +组合预测模型(BiLSTM-Attention + ARIMA)

目录 往期精彩内容&#xff1a; 前言 1 风速数据CEEMDAN分解与可视化 1.1 导入数据 1.2 CEEMDAN分解 2 数据集制作与预处理 2.1 划分数据集&#xff0c;按照8&#xff1a;2划分训练集和测试集&#xff0c; 然后再按照前7后4划分分量数据 2.2 设置滑动窗口大小为7&#…

SuperMap iClient3D for WebGL时序影像

文章目录 前言一、加载影像数据二、创建时间条1.这里使用Echarts来创建TimeLine&#xff0c;首先需要引入相关依赖2.初始化Echarts实例 三、设置不同年份影像交替显示四、效果 前言 时序影像可以用于对地球表面的变化进行定量分析和监测。 通过对多时相遥感影像的比较和分析&a…

【开源】基于Vue+SpringBoot的新能源电池回收系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户档案模块2.2 电池品类模块2.3 回收机构模块2.4 电池订单模块2.5 客服咨询模块 三、系统设计3.1 用例设计3.2 业务流程设计3.3 E-R 图设计 四、系统展示五、核心代码5.1 增改电池类型5.2 查询电池品类5.3 查询电池回…