温馨提示:jwt理论加强
第1步:创建maven项目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lian</groupId>
<artifactId>jwt</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jwt</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--JWT依赖,操作jwt的api-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
第2步:实际演练
package com.lian.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.Base64Codec;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 1、头部(Header)
* 头部用于描述关于该JWT的最基本的信息,例如其类型(即JWT)以及签名所用的算法(如
* HMAC SHA256或RSA)等。这也可以被表示成一个JSON对象
* { "alg": "HS256", "typ": "JWT" }
* typ :是类型
* alg :签名的算法,这里使用的算法是HS256算法
* 对头部的json字符串进行BASE64编码
* <p>
* 2、负载(Payload)
* 是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:
* ①标准中注册的声明(建议但不强制使用)
* iss: jwt签发者
* sub: jwt所面向的用户
* aud: 接收jwt的一方
* exp: jwt的过期时间,这个过期时间必须要大于签发时间
* nbf: 定义在什么时间之前,该jwt都是不可用的.
* iat: jwt的签发时间
* jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
* ②公共的声明
* 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不
* 建议添加敏感信息,因为该部分在客户端可解密
* ③私有的声明
* 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对
* 称解密的,意味着该部分信息可以归类为明文信息。
* 这个指的就是自定义的claim
* <p>
* 自定义claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对
* 这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉
* 接收方要对这些claim进行验证以及规则才行
* <p>
* 3、签证、签名(signature)
* jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
* 1. header (base64加密后的) 2. payload (base64加密后的) 3. secret(盐,一定要保密)
* 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然
* 后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
* <p>
* jwt的第1、2、3 部分均采用base64加密,然后再统一用 逗号 连接在一起生成号称 jwt
* <p>
* secret(盐值) 是保存在服务器端的, jwt 的签发生成也是在服务器端的, secret 就是用来进
* 行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。
* 一旦客户端得知这个 secret , 那就意味着客户端是可以自我签发 jwt 了
*/
@SpringBootTest
public class JwtApplicationTests {
//盐值
private static final String secret = "yjxxt";
/**
* 创建token
* token: eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjQxOTgyODk3fQ.vQLsYyb756qtOEoZGWLRXpELJyeMDd-INcDrUa8pfoA
* --------------------
* {"alg":"HS256"}
* {"jti":"888","sub":"Rose","iat":1641982897
* p:�k�_
*/
@Test
public void createToken() {
//创建一个JwtBuilder对象
JwtBuilder jwtBuilder = Jwts.builder()
//声明的标识{"jti":"888"}
.setId("888")
// 主体,用户{"sub":"Rose"}
.setSubject("Rose")
// 创建日期{"ita":"yjxxtxx"}
.setIssuedAt(new Date())
// 签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, secret);
//获取jwt的token
String token = jwtBuilder.compact();
System.out.println("token: " + token);
//三部分的base64解密
System.out.println("--------------------");
//token是3部分分别被base64加密后再用逗号拼接在一起
String[] split = token.split("\\.");
System.out.println(Base64Codec.BASE64.decodeToString(split[0]));
System.out.println(Base64Codec.BASE64.decodeToString(split[1]));
//无法解密
System.out.println(Base64Codec.BASE64.decodeToString(split[2]));
}
/**
* 解析token
* token相比session好处是,不必所有用户信息存在服务器中,服务器只需保存个 盐值,解析token就可验证用户
* 是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),
* 那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果
*/
@Test
public void parseToken() {
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjQxOTgyODk3fQ.vQLsYyb756qtOEoZGWLRXpELJyeMDd-INcDrUa8pfoA";
//解析token获取负载中的声明对象
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
//打印声明的属性
System.out.println("id:" + claims.getId());
System.out.println("subject:" + claims.getSubject());
System.out.println("issuedAt:" + claims.getIssuedAt());
}
/**
* token过期校验
* 我们并不希望签发的token是永久生效的,可以为token添加一个过期时间。原因:从服务器发出的token,服务器自己并不做记录,
* 就存在一个弊端就是,服务端无法主动控制某token的立刻失效
*/
@Test
public void verify() {
//当前系统时间的长整型
long now = System.currentTimeMillis();
//过期时间,这里是1分钟后的时间长整型
long exp = now + 60 * 1000;
//创建一个JwtBuilder对象
JwtBuilder jwtBuilder = Jwts.builder()
//声明的标识{"jti":"888"}
.setId("888")
//主体,用户{"sub":"Rose"}
.setSubject("Rose")
//创建日期{"ita":"yjxxtxx"}
.setIssuedAt(new Date())
//签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, "yjxxt")
//设置过期时间
.setExpiration(new Date(exp));
//获取jwt的token
String token = jwtBuilder.compact();
System.out.println(token);
}
/**
* 自定义 claims
* 以上例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims
*
* token: 3部分 头+体+盐
* eyJhbGciOiJIUzI1NiJ9.
* eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjQxOTg0MzQxLCJleHAiOjE2NDE5ODQ0MDEsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoibGlhbi5qcGcifQ.
* 4gJp8BR58RDMjy095RYLX4NpqgVp8v5A5oU3SecLyQ4
*/
@Test
public void claims() {
//当前系统时间的长整型
long now = System.currentTimeMillis();
//过期时间,这里是1分钟后的时间长整型
long exp = now + 60 * 1000;
//创建一个JwtBuilder对象
JwtBuilder jwtBuilder = Jwts.builder()
//声明的标识{"jti":"888"}
.setId("888")
//主体,用户{"sub":"Rose"}
.setSubject("Rose")
//创建日期{"ita":"yjxxtxx"}
.setIssuedAt(new Date())
//签名手段,参数1:算法,参数2:盐
.signWith(SignatureAlgorithm.HS256, "yjxxt")
//设置过期时间
.setExpiration(new Date(exp))
// 直接传入map // .addClaims(map)
.claim("roles", "admin")
.claim("logo", "lian.jpg");
//获取jwt的token
String token = jwtBuilder.compact();
System.out.println("token: "+token);
}
/**
* 解析自定义claims后的token
*/
@Test
public void claimsParse(){
String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjQxOTg0MzQxLCJleHAiOjE2NDE5ODQ0MDEsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoibGlhbi5qcGcifQ.4gJp8BR58RDMjy095RYLX4NpqgVp8v5A5oU3SecLyQ4";
//解析token获取负载中的声明对象
Claims claims = Jwts.parser().setSigningKey("yjxxt").parseClaimsJws(token).getBody();
//打印声明的属性
System.out.println("id:" + claims.getId());
System.out.println("subject:" + claims.getSubject());
System.out.println("issuedAt:" + claims.getIssuedAt());
DateFormat sf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("签发时间:"+sf.format(claims.getIssuedAt()));
System.out.println("过期时间:"+sf.format(claims.getExpiration()));
System.out.println("当前时间:"+sf.format(new Date()));
System.out.println("roles:"+claims.get("roles"));
System.out.println("logo:"+claims.get("logo"));
}
}