JSON Web Token (JWT): 理解与应用

JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。JWT通常用于身份验证和授权目的,因为它可以使用JSON对象在各方之间安全地传输信息

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

0.介绍

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

0.1.JWT的应用场景

身份验证 : 当用户成功登录时,服务器会生成一个JWT并将其发送给客户端。客户端在后续请求中将JWT附加到HTTP请求头中,以此来证明用户的身份。

授权 : JWT中可以包含用户的权限信息,这样服务器可以根据这些信息决定用户是否被允许访问某些资源。

信息传递 : 除了用户的身份和权限外,JWT还可以用来携带其他有用的信息,如用户的偏好设置等。

在 java 中 常与 Spring Security 框架配合使用

JWT的认证流程如下:

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探
  2. 后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串
  3. 后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可
  4. 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  5. 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  6. 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

在这里插入图片描述

0.2.优点

这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:

  • 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题

  • 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力

  • 更适用CDN:可以通过内容分发网络请求服务端的所有资料

  • 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多

  • 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

1.JWT的结构

JWT是一种自包含的令牌格式。JWT由三个部分组成:头部、载荷和签名。

在这里插入图片描述

1.1.头部 (Header)

头部通常包含两个部分:

  • typ: 表示该令牌的类型,通常是“JWT”。
  • alg: 指定签名算法,例如 HMAC SHA-256 或 RSA。

头部通常以JSON格式书写,并经过Base64Url编码。

1.2.载荷 (Payload)

载荷包含了需要作为声明传输的信息。这些声明可以分为三类:

  • 标准声明:由JWT规范定义的声明。
    • iss (issuer): 发行者。
    • sub (subject): 主题,通常是指用户ID。
    • aud (audience): 接收者,即令牌的预期受众。
    • exp (expiration time): 过期时间。
    • nbf (not before): 该时间之前不可使用。
    • iat (issued at): 发行时间。
    • jti (JWT ID): 一个唯一的标识符,用于防止重放攻击。
  • 私有声明:由发行者和接收者约定的声明,例如用户的角色或权限等。
  • 公共声明:虽然不是JWT规范的一部分,但可以在任何JWT中使用。

载荷也是经过Base64Url编码的。

1.3.签名 (Signature)

签名部分保证了JWT的完整性和安全性。签名通过将头部和载荷进行编码并使用指定的算法(如HMAC SHA-256 RSA或ECDSA)进行计算得到。签名确保了:

  • 令牌没有被篡改。
  • 令牌是由可信的一方发行的。

签名部分同样经过Base64Url编码。

1.4.JWS, JWK

JWS ,也就是JWT Signature,其结构就是在之前nonsecure JWT的基础上,在头部声明签名算法,并在最后添加上签名。创建签名,是保证jwt不能被他人随意篡改。我们通常使用的JWT一般都是JWS

为了完成签名,除了用到header信息和payload信息外,还需要算法的密钥,也就是secretKey。

加密的算法一般有2类:

  • 对称加密:secretKey指加密密钥,可以生成签名与验签
  • 非对称加密:secretKey指私钥,只用来生成签名,不能用来验签(验签用的是公钥)

JWT的密钥或者密钥对,一般统一称为JSON Web Key,也就是JWK

到目前为止,jwt的签名算法有三种:

  • HMAC【哈希消息验证码(对称)】:HS256/HS384/HS512
  • RSASSA【RSA签名算法(非对称)】 :(RS256/RS384/RS512)
  • ECDSA【椭圆曲线数据签名算法(非对称)】 :(ES256/ES384/ES512)

2.实现与工具

2.1.jjwt (Java JWT)

GitHub 仓库地址:https://github.com/jwtk/jjwt

这是一个基于Java的库,用于在JVM和Android平台上创建和验证JSON Web Tokens (JWTs)和JSON Web Keys (JWKs)。

它支持多种JOSE工作组的RFC规范:

  • RFC 7519: JSON Web Token (JWT)
  • RFC 7515: JSON Web Signature (JWS)
  • RFC 7516: JSON Web Encryption (JWE)
  • 等等
2.1.1.导入Maven依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
</dependency>
2.1.2.测试代码
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Key;
import java.util.Date;
import java.util.UUID;

@RestController
public class JwtController {

    // 密钥 : 实际开发时 应该从 配置文件 / 持久化存储中 获取
    private String secret;

    // token时效:24小时
    public static final long EXPIRE = 1000 * 60 * 60 * 24;


    @RequestMapping("/create")
    public String createToken(){

        // 生成一个随机的密钥 ID
        String keyId = UUID.randomUUID().toString();

        // 密钥
        secret = keyId;



        // 创建一个 JWT 令牌
        String jwt = Jwts.builder()

                // 设置JWT头部参数,指定令牌类型为JWT
                .setHeaderParam("typ", "JWT")
                // 设置JWT头部参数,指定签名算法为HS256
                .setHeaderParam("alg", "HS256")
                // 设置JWT过期时间,当前时间戳加上设定的过期时长
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                // 设置主题,通常是用户身份标识
                .setSubject("admin")
            
                // 添加角色声明
                .claim("role", "admin")
                // 设置用户ID为1
                .claim("id", 1)
                // 设置用户昵称为"王小二"
                .claim("nickname", "王小二")
            
                // 设置令牌签发时间
                .setIssuedAt(new Date())
                // 设置唯一标识符
                .setId( keyId )

                // 使用HS256算法和密钥签名
                .signWith(SignatureAlgorithm.HS256, secret.getBytes())


                // 将令牌压缩为紧凑形式的字符串
                .compact();

        return jwt;
    }


    @RequestMapping("/check")
    public  void checkToken(String jwt) {
        System.out.println("jwt = " + jwt);

        // 创建一个安全的密钥
        Key secureKey = Keys.hmacShaKeyFor(secret.getBytes());

        try {
            // 使用Jwts.parserBuilder()方法构建一个解析器,该解析器使用secureKey作为签名密钥
            // 然后使用这个解析器解析jwt字符串,获取到Claims对象,即JWT的主体部分
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(secureKey)
                    .build()
                    .parseClaimsJws(jwt)
                    .getBody();


            // 打印解析后的 JWT 信息
            System.out.println("Subject: " + claims.getSubject());
            System.out.println("Role: " + claims.get("role", String.class));
            System.out.println("Issue Time: " + claims.getIssuedAt());
            System.out.println("JWT ID: " + claims.getId());
            System.out.println("user nickname: " + claims.get("nickname"));

            // 可以在这里添加更多逻辑来验证 JWT 的有效性,例如检查过期时间等
            if(claims.getExpiration()==null){
                System.out.println("过期时间不能为空 Expiration time cannot be null");
            }

        } catch (Exception e) {
            // 如果 JWT 无法验证,这里会捕获异常
            System.err.println("Invalid JWT: " + e.getMessage());
        }
    }
}

2.2.Nimbus

Nimbus JOSE + JWT 是一个非常强大的 Java 库,用于处理 JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), JSON Web Token (JWT) 和 OAuth 2.0 授权服务器。这个库是由 NimbusDS 开发的,并且广泛应用于身份验证和授权系统中。

2.2.1.导入依赖

首先,您需要在 Maven 项目中添加 json-jwt 的依赖:

        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.40</version>
        </dependency>
2.2.2.测试代码

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.ParseException;
import java.util.Date;
import java.util.UUID;

@RestController
public class JwtController {

    // 密钥 : 实际开发时 应该从 配置文件 / 持久化存储中 获取
    private String secret;

    @RequestMapping("/create")
    public String createToken() {
        try {
            // 生成一个随机的密钥 ID
            String keyId = UUID.randomUUID().toString();

            // 密钥
            secret = keyId;

            // 创建一个 HMAC 签名器
            MACSigner signer = new MACSigner(secret.getBytes());


            // 设置 JWT 的声明
            JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
            builder.subject("admin");
            builder.claim("role", "admin");
            builder.issueTime(new Date());
            builder.jwtID(keyId);
            // 构建 JWT Claims Set
            JWTClaimsSet claimsSet = builder.build();
            // 创建一个空的 SignedJWT 对象
            SignedJWT signedJWT = new SignedJWT(
                    new JWSHeader(JWSAlgorithm.HS256),
                    claimsSet);
            // 签名 JWT
            signedJWT.sign(signer);
            // 将 JWT 转换为紧凑形式
            String jwt = signedJWT.serialize();

            return jwt;
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return null;
    }


    @RequestMapping("/check")
    public void checkToken(String jwt) {
        try {
            System.out.println("jwt = " + jwt);
            // 解析 JWT
            SignedJWT signedJWT = SignedJWT.parse(jwt);
            // 验证 JWT
            MACVerifier verifier = new MACVerifier( secret.getBytes() );
            boolean isValid = signedJWT.verify(verifier);
            if (!isValid) {
                System.out.println("无效的 JWT 签名 Invalid JWT signature.");
                return;
            }
            // 获取 JWT Claims
            JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
            System.out.println("Subject: " + claimsSet.getSubject());
            System.out.println("Role: " + claimsSet.getStringClaim("role"));
            System.out.println("Issue Time: " + claimsSet.getIssueTime());
            System.out.println("JWT ID: " + claimsSet.getJWTID());

        } catch (JOSEException | ParseException e) {
            e.printStackTrace();
        }
    }
    
    /**
    * token 通过 header传递
    * 再通过 request 取出
    */
    @RequestMapping("/checkHeader")
    public void checkHeaderToken(HttpServletRequest request) {
        try {
            String jwt = request.getHeader("Authorization");

            System.out.println("jwt header=> " + jwt);
            // 解析 JWT
            SignedJWT signedJWT = SignedJWT.parse(jwt);
            // 验证 JWT
            MACVerifier verifier = new MACVerifier( secret.getBytes() );
            boolean isValid = signedJWT.verify(verifier);
            if (!isValid) {
                System.out.println("无效的 JWT 签名 Invalid JWT signature.");
                return;
            }
            // 获取 JWT Claims
            JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet();
            System.out.println("Subject: " + claimsSet.getSubject());
            System.out.println("Role: " + claimsSet.getStringClaim("role"));
            System.out.println("Issue Time: " + claimsSet.getIssueTime());
            System.out.println("JWT ID: " + claimsSet.getJWTID());

        } catch (JOSEException | ParseException e) {
            e.printStackTrace();
        }
    }
}

2.2.3.请求测试
###
GET http://localhost:8080/create

生成token : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMzQ1MDgyOCwiaWF0IjoxNzIzMzY0NDI4LCJqdGkiOiI5Yzc3N2YwZC03NDU5LTQ3MTUtYmVkNy1mNWViYzJiNmMwOTgifQ.Y_P7L4gehl0kJwxTnwUxX8Yy502qrCHQ0hkxod58ly8

###
GET http://localhost:8080/check?jwt=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMzQ1MDgyOCwiaWF0IjoxNzIzMzY0NDI4LCJqdGkiOiI5Yzc3N2YwZC03NDU5LTQ3MTUtYmVkNy1mNWViYzJiNmMwOTgifQ.Y_P7L4gehl0kJwxTnwUxX8Yy502qrCHQ0hkxod58ly8

idea 控制台:
Subject: admin
Role: admin
Issue Time: Sun Aug 11 16:20:28 CST 2024
JWT ID: 9c777f0d-7459-4715-bed7-f5ebc2b6c098

###
GET http://localhost:8080/checkHeader
Content-Type: application/json
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcyMzQ1MDgyOCwiaWF0IjoxNzIzMzY0NDI4LCJqdGkiOiI5Yzc3N2YwZC03NDU5LTQ3MTUtYmVkNy1mNWViYzJiNmMwOTgifQ.Y_P7L4gehl0kJwxTnwUxX8Yy502qrCHQ0hkxod58ly8

idea 控制台:
Subject: admin
Role: admin
Issue Time: Sun Aug 11 16:20:28 CST 2024
JWT ID: 9c777f0d-7459-4715-bed7-f5ebc2b6c098

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

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

相关文章

【Python】函数进阶(中)

2、函数和函数名 函数名其实就是一个变量&#xff0c;这个变量只不过代指的是函数而已。 例如&#xff1a; 注意&#xff1a;函数必须先定义才能被调用执行&#xff08;Python是解释型语言&#xff0c;代码从上到下边解释边执行&#xff09; #正确代码 #错误代码 &#xff0…

20 数据可视化

20 数据可视化 本章概述一. `elasticsearch`实现数据统计1.1 创建用户信息索引1.1.1 控制台创建`aggs_user`索引1.1.2 `aggs_user`索引结构初始化1.1.3 创建`aggs_user`索引的`EO`对象1.1.4 用户类型枚举1.1.5 数据初始化****************************************************…

Redis中缓存穿透、缓存击穿、缓存雪崩的详解

如何理解Redis缓存的穿透、击穿、雪崩问题&#xff1a; 缓存穿透 是指缓存中和数据库中都没有数据&#xff0c;而用户不断访问&#xff0c;导致这个不存在的数据每次请求都要到存储层去查询&#xff0c;这样失去了意义。 缓存穿透的解决方案有哪些? 缓存null值布隆过滤增强…

C++观察者模式Observer

组件协作 –(都是晚绑定的&#xff09; ----观察者模式 为某些对象建立一种通知依赖的关系&#xff0c; 只要这个对象状态发生改变&#xff0c;观察者对象都能得到通知。 但是依赖关系要松耦合&#xff0c;不要太依赖。 eg&#xff1a;做一个文件分割器&#xff0c;需要一个…

基于ESP32的OEE分析开发板上MQTT协议的实现

整理自 《Implementation of MQTT Protocol on ESP32-Based OEE Analysis Development Board》&#xff0c;作者是Amir Akbar Wicaksono, Yuli Kurnia Ningsih, 和 Indra Surjati&#xff0c;发表于《MITOR: Jurnal Teknik Elektro》。论文讨论了在工业4.0背景下&#xff0c;通…

Centos7 message日志因dockerd、kubelet、warpdrive、containerd等应用迅速增长

问题&#xff1a;公司服务器在部署一套业务后&#xff0c;message日志记录大量的dockerd、kubelet、warpdrive、containerd应用日志&#xff0c;每天增加2G大小的日志 解决方案&#xff1a; 前期吐槽下&#xff1a;发现某个帖子&#xff0c;需要会员或者花钱才能看&#xff0c…

企业高性能web服务器知识点合集

文章目录 nginx源码编译安装平滑升级及版本回滚平滑升级版本回滚 服务启动脚本核心配置全局配置参数优化调整root与alias自定义错误日志自定义错误页面检测文件是否存在长链接配置下载服务器的配置 nginx高级配置nginx状态页面压缩功能变量内置变量自定义变量 nginx rewrite指令…

【软件测试面试题】WEB功能测试(持续更新)

Hi&#xff0c;大家好&#xff0c;我是小码哥。最近很多朋友都在说今年的互联网行情不好&#xff0c;面试很难&#xff0c;不知道怎么复习&#xff0c;我最近总结了一份在软件测试面试中比较常见的WEB功能测试面试面试题合集&#xff0c;希望对大家有帮助。 建议点赞收藏再阅读…

腾讯云 AI 代码助手四大基础功能介绍

引言 随着技术的不断进步&#xff0c;软件开发者们面临着日益复杂的编程任务和挑战。他们不仅需要处理大量的代码&#xff0c;还要在保证代码质量的前提下&#xff0c;提高开发效率。在这样的背景下&#xff0c;一款能够辅助开发者进行高效编码的工具显得尤为重要。 腾讯云AI…

sentinel 02 核心类

01 02. 03. 04. 05. 4.1 4.2 4.3 4.4 5调用链

从0到1:AI与低代码如何推动企业创新

引言 在当今瞬息万变的商业环境中&#xff0c;创新已成为企业在激烈市场竞争中立于不败之地的关键驱动力。面对快速变化的市场需求、技术进步和全球化竞争&#xff0c;企业亟需打破传统的增长模式&#xff0c;从而实现真正意义上的突破性创新。“从0到1”这一理念&#xff0c;源…

C++入门:C语言到C++的过渡

目录 前言&#xff1a;C——为弥补C缺陷而生的语言 C的输入与输出 1.引子&#xff1a;C的第一个程序 2.C的输入与输出 3.命名空间 疑难解答 1.对输入输出流的一个基本概念 2.从流当中存入及提取数据 前言&#xff1a;C——为弥补C缺陷而生的语言 C起源于 1979 年&…

微信小程序--24(列表渲染)

一、wx&#xff1a;for 1.作用 根据指定数组&#xff0c;循环渲染重复的组件结构 2.语法 <view wx:for"{{data中的数据}}"> 索引是&#xff1a;{{index}}, item项是&#xff1a;{{item}}</view> index:表索引item&#xff1a;表当前循环项 …

全国产化服务器:飞腾FT2000+/64核密集计算、显控及存储一体式加固服务器

飞腾FT2000/64核的加固服务器是一款专为高安全性、高可靠性及能在严苛环境下稳定运行而设计的服务器产品。以下是对该服务器的详细介绍&#xff1a; 一、处理器与核心组件 处理器&#xff1a;搭载飞腾FT2000/64核处理器&#xff0c;该处理器基于ARM架构&#xff0c;集成了64个…

KubeSphere核心实战_kubesphere部署redis01_为redis指定配置文件_指定存储卷_配置服务---分布式云原生部署架构搭建047

然后我们再来,部署一下redis,可以看到,首先去容器官网去找到对应的redis的镜像然后 可以看到镜像中都有说的,如何启动,以及 --appendonly yes 是指定持久化.然后 /data表示数据存储的位置. 可以看到数据存储位置 然后还有配置文件的位置. 可以看到,我们首先去创建配置文件,然后…

SQL进阶技巧:多维分析之如何还原任意维度组合下的维度列簇名称?【利用grouping_id逆向分析】

目 录 0 需求描述 1 数据准备 2 问题分析 3 小结 0 需求描述 现有用户访问日志表 visit_log ,每一行数据表示一条用户访问日志。 需求: (1)按照如下维度组合 (province), (province, city), (province, city, device_type) 计算用户访问量,要求一条SQL语句统计所所…

【Spring Boot】全局异常处理

目录 背景 前言 设计步骤 1.定义异常信息类&#xff1a; 2.自定义异常&#xff1a; 3.创建全局异常处理类 4.在控制器中抛出异常 5.输出 捕获 Valid 校验异常 背景 去面试的时候被问到SpringBoot项目中&#xff0c;如何处理全局异常的&#xff0c;也就是如何捕获全局异…

微服务及安全

一、微服务的原理 1.什么是微服务架构 微服务架构区别于传统的单体软件架构,是一种为了适应当前互联网后台服务的「三高需求:高并发、高性能、高可用」而产生的的软件架构。 单体式应用程序 与微服务相对的另一个概念是传统的单体式应用程序( Monolithic application ),…

Nginx源码安装与进阶负载均衡

目录 一 web服务介绍 二 Nginx介绍与源码安装 2.1 nginx源码安装 2.2 平滑升级和回滚 三 配置文件详解 3.1 配置文件说明 3.2 全局配置与http模块 3.3 root与alias 3.4 location的介绍 3.5 账户认证 四 高级配置 4.1 nginx 压缩功能 4.2 变量介绍 五 nginx反向代…

电脑硬盘坏了怎么恢复数据?

在数字化时代&#xff0c;电脑硬盘作为存储核心&#xff0c;承载着我们的工作文档、学习资料、家庭照片以及无数珍贵的回忆。然而&#xff0c;硬盘作为机械设备&#xff0c;也有其寿命和脆弱性&#xff0c;一旦出现故障&#xff0c;数据恢复便成为了一个紧迫而棘手的问题。本文…