掌握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

	@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。

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

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

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

相关文章

积水监测识别摄像机

积水监测识别摄像机是一种利用摄像技术来监测和识别道路、桥梁、隧道等区域积水情况的设备&#xff0c;它可以有效地提供实时的积水监测信息&#xff0c;帮助交通部门和相关单位及时采取应对措施&#xff0c;确保道路交通的畅通和人员安全。 积水监测识别摄像机通过安装在适当位…

如何使用支付宝沙箱环境支付并公网调用sdk创建支付单服务

文章目录 1.测试环境2.本地配置2. 内网穿透2.1 下载安装cpolar内网穿透2.2 创建隧道3. 测试公网访问4. 配置固定二级子域名4.1 保留一个二级子域名4.2 配置二级子域名5. 使用固定二级子域名进行访问 1.测试环境 MavenSpring bootJdk 1.8 2.本地配置 获取支付宝支付Java SDK,…

【深度学习-目标检测】03 - Faster R-CNN 论文学习与总结

论文地址&#xff1a;Faster R-CNN: Towards Real-Time ObjectDetection with Region Proposal Networks 论文学习 1. 摘要与引言 研究背景与挑战&#xff1a;当前最先进的目标检测网络依赖于 区域提议&#xff08;Region Proposals&#xff09;来假设目标的位置&#xff0c…

【Echarts】使用echarts和echarts-wordcloud生成词云图

一、下载echarts和echarts-wordcloud 地址&#xff1a;https://download.csdn.net/download/qq_25285531/88663006 可直接下载放在项目中使用 二、词云数据 词云数据是对象的格式&#xff0c;可以从后端获取&#xff0c;这里以下面数据为例 {"visualMap": 199,&…

Dash中的callback的使用 多input 6

代码说明 import plotly.express as pxmport plotly.express as px用于导入plotly.express模块并给它起一个别名px。这样在后续的代码中&#xff0c;你可以使用px来代替plotly.express&#xff0c;使代码更加简洁。 plotly.express是Plotly的一个子模块&#xff0c;用于快速创…

关于react

1.快速搭建开发环境 2.react渲染流程 3.1 jsx基础 概念 3.2 jsx基础 本质 3.3 jsx基础 jsx表达式 3.4 jsx基础 实现列表渲染 3.5 jsx基础 实现条件渲染 3.5 jsx基础 实现复杂的条件渲染 4. react中事件绑定 5.react组建基础使用 6.1 useState 6.2 useState修改状态的规则 7.基础…

Android 11.0 系统默认打开OEM解锁开关功能实现

1.前言 在11.0的系统rom定制化开发中,在9.0系统以后为了设备的安装,系统开始启用oem机制,所以在adb push文件就需要先oem解锁,然后才可以 进行相关操作,所以就需要默认打开oem解锁的开关,来方便oem解锁功能的实现 如图: 2.系统默认打开OEM解锁开关功能实现的核心类 pac…

《Git快速入门》Git分支

1.master、origin、origin/master 区别 首先搞懂git分支的一些名称区别&#xff1a; master &#xff1a; Git 的默认分支名字。它并不是一个特殊分支、跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支&#xff0c;是因为 git init 命令默认创建它&#xff0c…

web功能实例 - Canvas裁剪工具

嗯,手撸官方文档2天&#xff0c;发现没啥用&#xff0c;尤其是动画,那种计算出来的&#xff0c;根本想不到。因此学着学了抱着要做个东西的想法,去网上找相关案例,最终做出了这个裁剪工具。 PS :先说一下思路: 核心实现有3个canvas图层, 其中一个负责图片的预览。另外2个叠加到…

\r\n和缓冲区/进度条小程序

一 前置知识 带有\n就会立马刷新缓冲区&#xff0c;\r不会刷新缓冲区 刷新的2个场景: 1 ~fflush 缓冲区中存在\r或\n --> \r fflush --> 不换行的\n) 2 ~ 文件关闭自动刷新缓冲区 倒计时小程序0-9 倒计时小程序0-99

信号与线性系统翻转课堂笔记11——连续LTI系统频域分析

信号与线性系统翻转课堂笔记11——连续LTI系统频域分析 The Flipped Classroom11 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff0c;重点&…

Java基础知识复习

目录 一、Java语言基础知识&#xff0c;包括面向对象编程、语法特性等 0、什么是面向对象编程&#xff1f; 1、基本语法&#xff1a; 2、变量和数据类型&#xff1a; 3、运算符和表达式&#xff1a; 4、控制结构&#xff1a; 5、函数和方法&#xff1a; 6、类和对象&#xff1a…

因吹斯汀!只需上传照片,GPT-4V精准识别食物的卡路里和摄入热量

健身和减肥的朋友有福啦&#xff01; 最近一篇文章探索了GPT-4V在膳食评估领域的强大能力&#xff0c;可以根据饮食图片精准判断食物的种类与重量&#xff0c;并给出营养成分的分析&#xff0c;包括碳水化合物、蛋白质、脂肪占比。 最最重要的是&#xff0c;它还能告诉我们这…

算法与数据结构--二叉搜索树与自平衡二叉搜索树

0.字典&#xff08;即c的map&#xff09; 注&#xff1a;字典的 "member运算" 指的是检查字典中是否存在某个特定的键的操作&#xff0c;即查询操作。 如果我们使用数组来实现字典/map&#xff0c;虽然使用二分法查询也可以达到logn&#xff0c;但是的话插入和删除太…

GPT每预测一个token就要调用一次模型

问题&#xff1a;下图调用了多少次模型&#xff1f; 不久以前我以为是调用一次 通过看代码是输出多少个token就调用多少次&#xff0c;如图所示&#xff1a; 我理解为分类模型 预测下一个token可以理解为分类模型&#xff0c;类别是vocab的所有token&#xff0c;每一次调用都…

Description:An attempt was made to call a method that does not exist.

错误信息 Description: An attempt was made to call a method that does not exist. The attempt was made from the following location: okio.Segment.writeTo(Segment.kt:169) The following method did not exist: kotlin.collections.ArraysKt.copyInto([B[BIII)[B T…

多行文本(多行字符串)中,如果每行文本前都有空格,各行文本前空格数最小为n,则删除每行文本前的n个空格。textwrap.dedent(多行字符串)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 多行文本(多行字符串)中&#xff0c; 如果每行文本前都有空格&#xff0c; 各行文本前空格数最小为n&#xff0c; 则删除每行文本前的n个空格。 textwrap.dedent(多行字符串) 请问以下代…

ES的安装和RestClient的操作

目录 初识elasticsearch 什么是elasticsearch elasticsearch的发展 Lucene的优缺点 elasticsearch的优势 倒排索引 es与mysql的概念对比 文档 索引 概念对比 架构 安装es 安装kibana 安装ik分词器 分词器 安装ik分词器 ik分词器的拓展和停用词典 操作索引库…

c语言中数据结构

一、结构体的由来 1. 数据类型的不足 C语言中&#xff0c;基本数据类型只有整型、字符型、浮点型等少数几种&#xff0c;无法满足复杂数据类型的需要。 2. 数组的限制 虽然数组可以存储多个同类型的数据&#xff0c;但是数组中的元素个数是固定的&#xff0c;无法动态地改变…

Unity VR Pico apk安装失败:INSTALL_FAILED_UPDATE_INCOMPATIBLE

我的报错&#xff1a; PICO4企业版。安装apk&#xff0c;报错“安装失败。&#xff08;所属的Unity项目打包的apk&#xff0c;被我在同一台pico4安装了20次&#xff09; 调试方法&#xff1a; PIco4发布使用UNITY开发的Vr应用&#xff0c;格式为apk&#xff0c;安装的时候发生…