JWT令牌技术

目录

什么是令牌技术?

为什么要使用令牌技术?

JWT令牌

JWT令牌的生成和校验

令牌的优缺点


什么是令牌技术?

令牌技术: 是一种用于身份验证授权的方法,常用于网络安全和信息技术领域。在计算机系统中,令牌是一种用于证明用户身份或授权访问的凭证,可以是基于硬件的安全令牌,也可以是基于软件的生成代码或数据。令牌技术允许用户获取一个特定的令牌后,在之后的通信中使用该令牌来证明自己的身份或获得授权。

为什么要使用令牌技术?

我们通过一个例子:用户登录 来进一步理解令牌技术的使用

在实现用户登录时,我们可以使用 cookie 和 session 来验证用户身份:

  1. 用户提交用户名和密码后,登录页面将用户名、密码提交给服务器

  2. 服务器验证用户名密码是否正确

  3. 若验证成功,服务器创建一个 Session,并将SessionId 存储在 Cookie中,将用户身份信息存储在Session中

  4. 在之后的请求中,用户的游览器都会发送包含SessionId 的 Cookie 到服务器

  5. 服务器根据这个SessionId 来检索 Session 数据,并根据其中存储的身份信息来验证用户的身份

使用 cookie 和 session 实现身份验证时,需要服务器存储用户的身份信息到会话中,

然而,此时就会存在问题:集群环境下无法使用Session

对于开发的项目,当部署在一台机器上时,容易发送单点故障(当该服务器出现故障或需要维护时,所有用户的会话数据都会丢失,导致所有用户需要重新登录),因此,在通常情况下,一个Web应用会部署在多个服务器上,通过Nginx等实现负载均衡,此时来自用户的请求就会被分配到不同的服务器上

若使用Session进行会话跟踪,可能出现一些情况:

  1. 用户登录:用户登录请求,经过负载均衡,把请求转给了第一台服务器,第一台服务器进行账号密码验证,验证成功后,将Session存放在了第一台服务器上

  2. 查询操作:用户登录成功之后,携带Cookie(里面有SessionId)继续查询操作,此时请求转发到了第二台机器,第二台机器会进行权限验证操作(通过SessionId验证用户是否登录),此时由于第二台机器上未存储该用户的Session,就会出现问题

为了解决上述问题,我们可以将 Session 存储在可靠的持久化存储中,如数据库或缓存服务

但此时就需要存储大量用户数据信息

为了解决上述问题,我们可以使用令牌技术:

  1. 当用户提交账号和密码后,服务器进行校验

  2. 校验成功,生成令牌(Token),将其返回给客户端

  3. 客户端携带 Token 再次进行访问,服务器对 Token 进行校验

  4. 服务器校验 Token 的真假,若为真,则提供服务

此时,由客户端存储令牌,减轻了服务器的存储压力,且在集群环境下也能进行认证

我们再通过一个生活中的例子进一步理解令牌技术:

令牌就类似于我们的身份证:

用户提供户口本等信息,交由公安局进行校验,公安局校验成功后,发放身份证

用户保存身份证

用户上学、出行、旅游等携带身份证

酒店等通过公安局提供的专用设备核验身份证真假,若为真,则提供服务

JWT令牌

我们可以通过令牌来解决上述问题:

**令牌(Token)**的本质就是一个字符串,其实现方式很多,如JWT令牌、刷新令牌、Bearer令牌等

在这里,我们学习JWT令牌

**JWT令牌:JWT(JSON Web Token)**是一种开放的标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。JWT 可以通过数字签名或加密来验证其可靠性,通常用于在客户端和服务器之间安全地传递身份验证信息

JWT官网:JSON Web Tokens - jwt.io

JWT由三部分组成,分别是头部(Header)载荷(Payload)签名(Signature),每个部分之间使用 . 分割

头部(Header) :包括令牌类型 (即JWT)和使用的哈希算法(如 HMAC、SHA256)

载荷(Payload):存放有效信息,其中是一些自定义的内容(如:{"userName": "zhangsan"}),也可以存储JWT提供的默认字段,如 exp(过期时间戳)等

JWT指定了七个默认字段:

iss(Issuer):该JWT的签发者

sub(Subject):令牌的主题,表示该JWT的主体,即该JWT所代表的用户或实体

aud(Audience):JWT的受众,该JWT的使用对象

exp(Expiration Time):JWT的过期时间,表示该JWT的有效期限,超过该时间后JWT将不再可用

nbf(Not Before):JWT的生效时间,在该时间之前,该JWT都是不可用的

iat(Issued At):JWT的签发时间

jti(JWT ID):JWT的唯一标识符,表示该JWT的唯一性,可用于防止重放攻击

签名(Signature) :用于验证消息的完整性和可靠性,防止JWT内容被篡改,确保安全性

签名用于验证消息的完整性和可靠性,JWT中任何一个字符被篡改,整个令牌都会校验失败

但其不确保其保密性,即不能防止被解析,也就是任何人都可以看到JWT中的信息(就如身份证,任何人都可以看到身份证上的信息,但不能篡改身份证上的信息)

因此 JWT令牌不适合存放敏感信息(如用户密码等)

当没有签名时,我们仍然能够看到JWT中的信息,即令牌不保证保密性

在了解了JWT令牌后,我们来学习JWT令牌的生成和校验

JWT令牌的生成和校验

首先,我们需要引入 JWT 令牌的依赖

java 复制代码
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

接着,我们就可以使用Jar包中提供的API来完成JWT令牌的生成和校验了

我们首先来看生成令牌:

Header

JWT的头部包含两部分信息:

  1. 令牌类型(alg):JWT

  2. 加密算法(typ):可选择 HS256、HS384、HS512、RS256...

java 复制代码
        //头部
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        //生成令牌
        String token = Jwts.builder()
                .setHeader(header) // 可以不设定,此时使用默认的

Payload:

Paylod通常是一个JSON对象,包含了JWT的所有声明(claims)以及其他需要传递的信息

载荷包含两种类型数据:

  1. 自定义数据

  2. 标准中注册的声明数据

java 复制代码
        long JWT_EXPIRATION = 60 * 60 * 1000;
// 自定义信息
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", 1);
        claim.put("userName", "zhangsan");
        //生成令牌
        String token = Jwts.builder()
                .setClaims(claim) // 自定义信息
                .setIssuedAt(new Date()) // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) // 过期时间

Signature:

签名内容需要通过计算得出:

base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + your-256-bit-secret

将待签名消息进行编码:

通常采用Base64编码将 header 和 payload转换为字符串

选择密钥:

我们以 HS256(HMAC with SHA-256)加密算法为例,我们需要选择一个密钥(密钥是进行签名计算的关键)

使用加密算法计算签名:

然后通过 header 中声明的加密算法 进行签名计算(仍以 HS256为例),将 编码后的消息 和 密钥输入 HMAC 算法中,进行 SHA-256哈希处理,生成签名

将签名添加到JWT中:

将生成的签名添加到JWT的尾部,形成最终的JWT

我们首先生成随机密钥:

java 复制代码
     @Test
    //生成随机密钥
    public void genKey() {
        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String secretStr = Encoders.BASE64.encode(secretKey.getEncoded());
        System.out.println(secretStr);
    }

运行,得到密钥:

以运行结果作为随机密钥:

java 复制代码
        String secretStr = "IufNeZtdSwoMADOxF3yofGqrpjAhNx58C/bomQ27NKg=";
        Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));
        
        // 生成令牌
        String token = Jwts.builder()
                .setHeader(header) // 可以不设定,此时使用默认的
                .setClaims(claim) // 自定义信息
                .setIssuedAt(new Date()) // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) // 过期时间
                .signWith(key)
                .compact();

生成令牌:

java 复制代码
    private static final long JWT_EXPIRATION = 60 * 60 * 1000;
    @Test
    public void genJwt() {
        //头部
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        // 自定义信息
        Map<String, Object> claim = new HashMap<>();
        claim.put("id", 1);
        claim.put("userName", "zhangsan");
        // 生成密钥
        String secretStr = "IufNeZtdSwoMADOxF3yofGqrpjAhNx58C/bomQ27NKg=";
        Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));
        // 生成令牌
        String token = Jwts.builder()
                .setHeader(header) // 可以不设定,此时使用默认的
                .setClaims(claim) // 自定义信息
                .setIssuedAt(new Date()) // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + JWT_EXPIRATION)) // 过期时间
                .signWith(key) // 签名
                .compact();
        System.out.println(token);
    }

运行,查看得到的令牌:

我们将得到的令牌复制到官网上解析:

将密钥复制到 your-256-bit-secret位置

校验通过

接下来,我们实现令牌的校验:

我们需要使用之前生成的随机密钥来进行解密:

java 复制代码
    private static final String secretStr = "IufNeZtdSwoMADOxF3yofGqrpjAhNx58C/bomQ27NKg=";
    private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretStr));

    @Test
    public void parseToken() {
        // 令牌
        String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlck5hbWUiOiJ6aGFuZ3NhbiIsImlhdCI6MTcxNzgyMDA3MywiZXhwIjoxNzE3ODIzNjczfQ.MKqOIgYTmmJMPktpbjsWq4WDmkuceCQPq2tJQoJVSpE";
        // 解析令牌
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims claims = build.parseClaimsJws(token).getBody();
        System.out.println(claims);
    }

运行,我们可以看到解析的消息

令牌的优缺点

优点:

  1. 解决了集群环境下的认证问题

  2. 减轻了服务器的存储压力,即无需在服务器端存储

  3. 令牌可以通过加密或签名等方式进行安全传输和存储,减少了被盗用的风险,更安全

缺点:

  1. 相对于传统的基于会话的认证机制,令牌认证通常需要更多的技术和知识,包括令牌签发、验证、刷新等流程,因此实现和维护的成本较高

  2. 如果令牌泄露,可能会导致安全风险,因此需要进行令牌的安全存储和传输

相关推荐
.生产的驴几秒前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
旦沐已成舟2 小时前
DevOps-Jenkins-新手入门级
服务器
码蜂窝编程官方3 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
软件技术员3 小时前
Let‘s Encrypt SSL证书:acmessl.cn申请免费3个月证书
服务器·网络协议·ssl
AuroraI'ncoding3 小时前
时间请求参数、响应
java·后端·spring
一条晒干的咸魚3 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
东华果汁哥4 小时前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
mengao12344 小时前
centos 服务器 docker 使用代理
服务器·docker·centos
C-cat.4 小时前
Linux|进程程序替换
linux·服务器·microsoft
怀澈1224 小时前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++