JSON Web Token

news2025/1/16 12:46:19

目录

  • 1. 概念
    • 1. JWT 概述
    • 2. session认证流程
    • 2. JWT认证流程
  • 2. 使用JWT
    • 1. 获取令牌
    • 2. 验证令牌
    • 3. 封装工具类
  • 3. Springboot整合JWT
    • 1. 项目搭建
    • 2. 使用JWT
    • 3. 优化代码

1. 概念


1. JWT 概述

JWT:

  • 概念:
    • 通过 JSON 形式作为 Web 应用中的令牌,用于在各方之间安全地将信息作为 JSON 对象,安全地传输信息
    • 在数据传输过程中可以对数据进行加密,签名等处理
    • 开销小,可在多种域中使用
  • 授权
    • 一旦用户登录,每个后续请求将包括 JWT,从而允许用户访问该令牌允许的路由、服务和资源
  • 信息安全
    • 可对 JWT 进行签名(公钥/私钥),可验证请求发送者身份信息(认证)
    • JWT 签名使用表头和有效负债计算,可以验证内容是否被篡改

2. session认证流程

传统session认证:

  • 概念:

    • http——一种无状态的协议,无法存储用户信息,用户每次请求都需要认证
    • 为了方便识别发起请求的用户信息,需要在服务器上存储用户信息,保存为 cookie,下次请求就可以识别发起请求的用户
  • 演示:

    @RestController
    public class Controller {
        @RequestMapping("test")
        public Object test(String name, HttpServletRequest req) {
            req.getSession().setAttribute("name", name);
    
            return req.getSession().getAttribute("name");
        }
    }
    
    • 第一次请求会返回一个 cookie


      后续请求将不再返回cookie
  • 缺陷:

    • 每个用户请求之后,都需要在服务端做一次记录(保存在内存中),开销较大
    • 认证的记录保存在上次访问的服务器的内存中,扩展能力弱
    • cookie 被截获,用户容易受到跨站请求伪造的攻击

2. JWT认证流程

JWT认证:

  • 认证流程:

    • 前端通过 web 表单将用户名、密码发送到后端接口
    • 后端验证用户信息成功后,将用户信息作为 JWT Payload(负载),将其与头部分别进行 Base64 编码拼接后签名(JWT(Token) —— 形同 111.zzz.xxx 的字符串
    • 后端将 JWT 字符串作为登陆成功的返回结果返回给前端,前端将返回的结果存在 localStoragesessionStorage ,退出登录时,前端删除保存的 JWT
    • 前端每次请求时将 JWT 放入 http headerauthorization 位(授权位,解决 XSSXSRF 问题)
    • 后端检查请求是否存在 JWT,如果存在就验证其有效性(是否正确,是否过期,接收方是否是自己…)
    • 验证通过,就能调用后端的接口,执行业务
  • 优势:

    • 简洁:数据量小,可通过 urlpost 参数 或者 http header 发送
    • 自包含:负载中包含了用户所需要的信息,不需要多次查询数据库
    • 采用 JSON 加密的形式保存在客户端,跨语言
    • 不需要在服务器保存会话信息(避免内存占用),适用于微服务
  • 结构:

    • 3段式字符串:header.payload.signature
      • header:标头
        • 通常包含两部分:签名使用的算法(HMAC、SHA256(默认)、RSA) + 令牌类型
          {
          	"alg": "SHA256",
          	"type": "JWT"
          }
          
        • 然后,使用 Base64 编码 此 JSON 对象 → header 字符串
      • payload:有效负载
        • 包含 声明:有关实体(如用户)和其他数据的声明
        • 在有效负载中,不要放用户的敏感信息(如:用户密码等)
          {
          	"name": "zhangsan"
          	"admin": true
          }
          
        • 然后,使用 Base64 编码 此 JSON 对象 → payload 字符串
      • signature:签名
        • 加密:Base64编码后的 header + payload + 盐(签名),然后使用 header 中 指定的签名算法(SHA256)进行签名
        • 验签:根据请求中的 JWT 进行一次签名加密生成一个 signature1,然后去和请求中的 JWTsignature 进行比对

2. 使用JWT

依赖:

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

1. 获取令牌

测试类:

public class JwtCreate {
    @Test
    public void create() {
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, 3); // 3天

        Map<String, Object> header = new HashMap<>();
        header.put("alg", "SHA256");
        header.put("type", "JWT");
        String token = JWT.create()
                .withHeader(header) // header
                .withClaim("uid", "001") // payload
                .withClaim("name", "zhangsan") // payload
                .withClaim("admin", true) // payload
                .withExpiresAt(instance.getTime()) // 指定令牌过期时间(3天后过期)
                .sign(Algorithm.HMAC256("chen1020")); // signature
        System.out.println(token);
    }
}

输出:

注意:

  • .withHeader(header) 一般不写,直接用默认配置
  • chen1020 是加密所用的盐值,自定义

2. 验证令牌

测试方法:

    @Test
    public void verify() {
        // 创建验证对象
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256("chen1020")).build();
        // 验证token
        DecodedJWT verify = verifier.verify("eyJ0eXAiOiJKV1QiLCJ0eXBlIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJ1aWQiOiIwMDEiLCJuYW1lIjoiemhhbmdzYW4iLCJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjY5ODc1MTI3fQ.-eewHSjiqXQggakhJayx2mOfqmRS8iz4Ockb_BKg_0o");
        // 获取用户信息(验证通过后才能获取)
        System.out.println(verify.getClaims());
        System.out.println("-------------------------------------");
        System.out.println("uid: " + verify.getClaims().get("uid") + " name: " + verify.getClaims().get("name"));
        System.out.println("-------------------------------------");
        System.out.println("uid: " + verify.getClaim("uid") + " name: " + verify.getClaim("name"));
    }

输出:

注意:

  • 一个 withClaim 中,一个 key 只能对应一个 value,后面的 value 会覆盖前面的 value
  • 可以使用 withArrayClaim(String name, xxx[] xxx) ,放多个 value

验证令牌的过程: 验证签名(SignatureVerificationException) → token是否过期(TokenExpiredException) → 签名算法(AlgorithmMismatchException

3. 封装工具类

public class JWTUtils {

    // 盐值
    private static final String SALT = "LyeXro0VaE^!p";

    /**
     * @Description 创建token
     * @param map 负载map
     * @return String
    */
    public static String getToken(Map<String, String> map) {
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE, 5);

        JWTCreator.Builder builder = JWT.create();

        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });

        String token = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SALT));

        return token;
    }

    /**
     * @Description 验证token合法性,不合法就会抛出异常
     * @param token 
    */
    public static DecodedJWT verify(String token) {
        return JWT.require(Algorithm.HMAC256(SALT)).build().verify(token);
    }
    
}

3. Springboot整合JWT

1. 项目搭建

依赖:

        <!--jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.3</version>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.11</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

配置文件:

# 应用名称
spring:
  application:
    name: jwt_demo
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spbt?serverTimezone=GMT&characterEncoding=utf-8&useSSL=false
    username: root
    password: admin

# 应用服务 WEB 访问端口
server:
  port: 8088

表:

实体类:

@Data
public class User {
    private Integer id;
    private String name;
    private Character status;
    private String password;
}

mapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.chenjy.jwt_demo.mapper.UserMapper">

</mapper>

mapper接口

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

service:

public interface UserService extends IService<User> {
    User login(User user);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{
    @Resource
    private UserMapper userMapper;


    @Override
    public User login(User user) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name", user.getName());
        User userMsg = userMapper.selectOne(wrapper);
        if (userMsg == null) throw new RuntimeException("用户不存在, 登陆失败");
        if (!userMsg.getPassword().equals(user.getPassword())) throw new RuntimeException("密码错误, 登陆失败");
        return userMsg;
    }
}

controller:

@RestController
@Slf4j
public class Controller {
    @Resource
    private UserService userService;

    @RequestMapping("/login")
    public Map<String, Object> login(User user) {
        Map<String, Object> map = new HashMap<>();
        try {
            User userMsg = userService.login(user);
            map.put("state", true);
            map.put("smg", "登陆成功");
        } catch (RuntimeException e) {
            map.put("state", false);
            map.put("smg", "登陆失败");
            e.printStackTrace();
        }
        return map;
    }
}

测试:

2. 使用JWT

@RestController
@Slf4j
public class Controller {
    @Resource
    private UserService userService;

    @RequestMapping("/login")
    public Map<String, Object> login(User user) {
        Map<String, Object> map = new HashMap<>();
        try {
            User userMsg = userService.login(user);
            // payload
            Map<String, String> payload = new HashMap<>();
            payload.put("id", userMsg.getId() + "");
            payload.put("name", userMsg.getName());
            payload.put("status", userMsg.getStatus() + "");
            // 生成令牌
            String token = JWTUtils.getToken(payload);

            map.put("state", true);
            map.put("smg", "登陆成功");
            map.put("token", token);
        } catch (RuntimeException e) {
            map.put("state", false);
            map.put("smg", "登陆失败");
            e.printStackTrace();
        }
        return map;
    }

    @RequestMapping("/test")
    public Map<String, Object> test(String token) {
        Map<String, Object> map = new HashMap<>();
        log.info("token:" + token);
        try {
            JWTUtils.verify(token);
            map.put("state", true);
            map.put("smg", "登陆成功");
        } catch (SignatureVerificationException e) {
            map.put("state", false);
            map.put("smg", "签名错误");
        } catch (TokenExpiredException e) {
            map.put("state", false);
            map.put("smg", "token过期");
        } catch (AlgorithmMismatchException e) {
            map.put("state", false);
            map.put("smg", "算法不一致");
        } catch (Exception e) {
            map.put("state", false);
            map.put("smg", "无效签名");
        }
        return map;
    }
}

3. 优化代码

拦截器:

public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求头数据
        String token = request.getHeader("token");

        Map<String, Object> map = new HashMap<>();
        // 验证令牌
        try {
            JWTUtils.verify(token);
            return true; // 验证通过
        } catch (SignatureVerificationException e) {
            map.put("state", false);
            map.put("smg", "签名错误");
        } catch (TokenExpiredException e) {
            map.put("state", false);
            map.put("smg", "token过期");
        } catch (AlgorithmMismatchException e) {
            map.put("state", false);
            map.put("smg", "算法不一致");
        } catch (Exception e) {
            map.put("state", false);
            map.put("smg", "无效签名");
        }
        
        map.put("state", false);
        // 将map转换为json 
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;character=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

注册拦截器:

@Configuration
public class InterceptorConf implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry
                .addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

test接口:

    @RequestMapping("/test")
    public Map<String, Object> test(String token) {
        Map<String, Object> map = new HashMap<>();
        map.put("state", true);
        map.put("msg", "请求成功");
        return map;
    }

测试:

注意: 即使重启项目,用之前的 token 依然可以请求成功。

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

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

相关文章

[附源码]计算机毕业设计springboot贷款申请审核管理系统论文

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

「Vue3」手把手教你使用 Vite 快速搭建项目

本项目选型默认使用vue3 typescript vite 1. 使用vite初始化项目 vite创建地址&#xff0c;创建完成后有一个基本的项目结构了&#xff0c;如下图 2. 配置vite.config.ts&#xff0c;配置详解 import { defineConfig } from viteimport path from path;import vue from …

【PS-6】视图操作

目录 旋转视图工具 选项栏中的“滚动所有窗口 用【空格键】移动视图 将图片放大缩小的方法 1、缩放工具 2、通过鼠标右键对画面进行放大或缩小 3、【细微缩放】按钮 4、通过【CTRL /-】放大缩小 5、窗口→导航器 旋转视图工具 此工具常用于与数位板的结合使用 位置&…

北大惠普金融指数-匹配企业绿色创新指数2011-2020年:企业名称、年份、行业分类等多指标数据

1、数据来源&#xff1a;北京大学数字金融中心、国家统计局、国家专利产权局等部门公开数据 2、时间跨度&#xff1a;2011-2020年 3、区域范围&#xff1a;全国 4、指标说明&#xff1a; 中国内地31个省&#xff08;直辖市、自治区&#xff0c;简称“省”&#xff09;、337…

Rust机器学习之Linfa

Rust机器学习之Linfa 众所周知&#xff0c;Python之所以能成为机器学习的首选语言&#xff0c;与其丰富易用的库有很大关系。某种程度上可以说是诸如numpy、pandas、scikit-learn、matplotlib、pytorch、networks…等一系列科学计算和机器学习库成就了Python今天编程语言霸主的…

【MAX7800实现KWS20 demo演示】

【MAX7800实现KWS20 demo演示】1. 概述2. 关键字定位演示2.1 构建固件&#xff1a;2.2 选择板卡2.3 MAX78000 EVKIT2.3.1 MAX78000 EVKIT下载程序2.3.2 MAX78000 EVKIT 跳线设置2.3.3 MAX78000 EVKIT 操作2.4 MAX78000 Feather2.4.1 MAX78000 Feather羽毛板下载固件2.4.2 MAX78…

IBM MQ MQCSP

一&#xff0c;概念 1.1 用途 用途&#xff1a;MQCSP 结构使授权服务能够验证用户 ID 和密码。您在 MQCONNX 调用上指定 MQCSP 连接安全参数结构。 警告&#xff1a;在某些情况下&#xff0c;客户端应用程序的 MQCSP 结构中的密码将以纯文本形式通过网络发送。要确保客户端应…

【学习笔记58】JavaScript面向对象

一、认识面向对象 &#xff08;一&#xff09;面向过程编程 按照程序执行的过程一步一步的完成程序代码 &#xff08;二&#xff09;面向对象编程 面向对象编程是一种编程的方式/模式官方&#xff1a;对一类具有相同属性和功能的程序代码抽象的描述&#xff0c;实现代码编程…

Triangle Attack: A Query-efficient Decision-based Adversarial Attack

Triangle Attack: A Query-efficient Decision-based Adversarial Attack 三角攻击:一种查询高效的基于决策的对抗性攻击 Abstract 基于决策的攻击对实际应用程序构成了严重的威胁&#xff0c;因为它将目标模型视为一个黑箱&#xff0c;只访问硬预测标签。最近已经做出了很大…

【计组】指令和运算1--《深入浅出计算机组成原理》(二)

一、计算机指令 1、指令 从软件工程师的角度来讲&#xff0c;CPU就是一个执行各种计算机指令&#xff08;Instruction Code&#xff09;的逻辑.。 这里的计算机指令&#xff0c;也可以叫做机器语言。 不同发CPU支持的机器语言不同&#xff0c;如个人电脑用的是Intel的CPU&a…

同样Java后端开发三年,朋友已经涨薪到了30k,而我才刚到12K。必须承认多背背八股文确实有奇效!

程序猿在世人眼里已经成为高薪、为人忠诚的代名词。 然而&#xff0c;小编要说的是&#xff0c;不是所有的程序员工资都是一样的。 世人所不知的是同为程序猿&#xff0c;薪资的差别还是很大的。 众所周知&#xff0c;目前互联网行业是众多行业中薪资待遇最好的&#xff0c;…

2022年NPDP新版教材知识集锦--【第四章节】(2)

【概念设计阶段】(全部获取文末) 概念描述提供了产品概念的优点和特征的定性描述&#xff0c;其必要性体现在&#xff1a; ①为开发团队的所有成员以及与项目相关的成员提供了清晰性和一致性。 ②是向潜在客户解释产品的重要手段之一。 典型流程&#xff1a; 2.1概念工程 …

python使用websocket服务并在fastAPI中启动websocket服务

依赖 pip install websockets-routes 代码 import asyncio import websockets import websockets_routes from websockets.legacy.server import WebSocketServerProtocol from websockets_routes import RoutedPath# 初始化一个router对象 router websockets_routes.Router()…

Archlinux安装软件的那些事

个人主页&#xff1a;董哥聊技术我是董哥&#xff0c;嵌入式领域新星创作者创作理念&#xff1a;专注分享高质量嵌入式文章&#xff0c;让大家读有所得&#xff01;文章目录1、ArchLinux1.1 ArchLinux原则1.2 软件包管理1.2.1 软件仓库1.2.2 包管理器2、Pacman2.1 pacman介绍2.…

什么是幂等性?四种接口幂等性方案详解!

幂等性在我们的工作中无处不在&#xff0c;无论是支付场景还是下订单等核心场景都会涉及&#xff0c;也是分布式系统最常遇到的问题&#xff0c;除此之外&#xff0c;也是大厂面试的重灾区。 知道了幂等性的重要性&#xff0c;下面我就详细介绍幂等性以及具体的解决方案&#…

SpringBoot中自动配置

第一种&#xff1a; 给容器中的组件加上 ConfigurationProperties注解即可 测试&#xff1a; Component ConfigurationProperties(prefix "mycar") public class Car {private String brand;private Integer price;private Integer seatNum;public Integer getSeat…

币圈已死,绿色积分是全新的赛道吗?

近几年来&#xff0c;移动互联网行业的迅猛发展&#xff0c;快速改变着社会业态。尽管如此&#xff0c;仍有大量企业线上线下处于割裂状态&#xff0c;2020 年一场疫情的突然爆发&#xff0c;并持续到 2022年&#xff0c;对零售行业造成流量崩塌、供应链中断、市场供需下滑等压…

现代 CSS 高阶技巧,完美的波浪进度条效果。

将专注于实现复杂布局&#xff0c;兼容设备差异&#xff0c;制作酷炫动画&#xff0c;制作复杂交互&#xff0c;提升可访问性及构建奇思妙想效果等方面的内容。 在兼顾基础概述的同时&#xff0c;注重对技巧的挖掘&#xff0c;结合实际进行运用&#xff0c;欢迎大家关注。 正…

金属非金属如何去毛刺 机器人浮动去毛刺

毛刺的产生 在金属非金属零件的加工中&#xff0c;由于切削加工过程中塑性变形引起的毛边&#xff0c;或者是铸造、模锻等加工的飞边&#xff0c;或是焊接挤出的残料&#xff0c;这些与所要求的形状、尺寸有所出入&#xff0c;在被加工零件上派生出的多余部分即为毛刺&#xf…

音视频开发之 ALSA实战!

前言&#xff1a; 今天我们来分享一个开源的音频采集代码&#xff0c;现在大部分音频采集都是通过ALSA框架去采集&#xff0c;如果大家把ALSA采集代码学懂&#xff0c;那么大部分的音频采集都可以搞定。这个代码是用ALSA进行音频PCM的采集并保存到本地文件。一、alsa框架的介绍…