1. 认识JWT
1.1 什么是JWT
Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递 json 对象,传递的信息经过数字签名可以被验证和信任。JWT 可以使用 HMAC 算法或使用 RSA 的公钥/私钥对来签名,防止被篡改。
优点:1. jwt 基于 json,非常方便解析。2. 可以在令牌中自定义丰富的内容,易扩展。3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。4. 资源服务使用JWT可不依赖认证服务即可完成授权。缺点:1. JWT令牌较长,占存储空间比较大。
1.2 JWT构成
1.2.1 头部
JWT 由三部分构成:头部、负载、签证。JWT 的头部承载两部分信息,声明类型以及加密算法。
- 加密解密网站:https://tool.chinaz.com/tools/base64.aspx ```java { ‘typ’: ‘JWT’, ‘alg’: ‘HS256’ }
// 头部使用Base64加密 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
<a name="J3Q0Y"></a>### 1.2.2 负载负载,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:- 标准中注册的声明(建议但不强制使用)- **iss**: jwt 签发者- **sub**: jwt 所面向的用户- **aud**: 接收 jwt 的一方- **exp**: jwt 的过期时间,这个过期时间必须要大于签发时间- **nbf**: 定义在什么时间之前,该 jwt 都是不可用的.- **iat**: jwt 的签发时间- **jti**: jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。- 公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.- 私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。```java{"sub": "1234567890","name": "John Doe","admin": true}// 加密eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
1.2.3 签证
jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
这个部分需要 base64 加密后的 header 和 base64加密后的 payload 使用.连接组成的字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了jwt的第三部分。
注意:secret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发 jwt 了。
1.3 JWT应用
一般是在请求头里加入 Authorization,并加上Bearer 标注:
fetch('api/user/1', {headers: {'Authorization': 'Bearer ' + token}})
服务端会验证 token,如果验证通过就会返回相应的资源。整个流程就是这样的:
2. 认识JJWT
2.1 什么是JJWT
JJWT 是一个提供端到端的 JWT 创建和验证的 Java 库。永远免费和开源(Apache License,版本2.0),JJWT 很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。规范官网:https://jwt.io/
2.2 引入依赖
<!-- jwt 依赖配置--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
2.3 快速入门
1、引入依赖。
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springsecurity-demo</artifactId><groupId>com.xuwei</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>springboot-jjwt</artifactId><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!-- jwt 依赖配置--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</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></project>
2、自定义 JWT 测试类。
@SpringBootTestpublic class JjwtTest {/*** 创建Token*/@Testpublic void testCreateToken() {System.out.println("获取token");// 创建一个JwtBuilder对象JwtBuilder jwtBuilder = Jwts.builder()// 声明的标识{"jti":"888"}.setId("888")// 主体,用户{"sub":"Rose"}.setSubject("Rose")// 创建日期{"ita":"xxxxxx"}.setIssuedAt(new Date())// 签名手段,参数1:算法,参数2:盐.signWith(SignatureAlgorithm.HS256, "xxxx");// 获取jwt的tokenString token = jwtBuilder.compact();System.out.println(token);// 三部分的base64解密String[] tokenSplit = token.split("\\.");// {"alg":"HS256"}System.out.println(Base64Codec.BASE64.decodeToString(tokenSplit[0]));// {"jti":"888","sub":"Rose","iat":1646360468System.out.println(Base64Codec.BASE64.decodeToString(tokenSplit[1]));// 无法解密System.out.println(Base64Codec.BASE64.decodeToString(tokenSplit[2]));}/*** token过期校验*/@Testpublic void testCreateTokenHasExp() {long now = System.currentTimeMillis();// 设置过期时间,这里是1分钟后的时间长整型long exp = now + 60 * 1000;JwtBuilder jwtBuilder = Jwts.builder()//声明的标识{"jti":"888"}.setId("888")//主体,用户{"sub":"Rose"}.setSubject("Rose")//创建日期{"ita":"xxxxxx"}.setIssuedAt(new Date())//签名手段,参数1:算法,参数2:盐.signWith(SignatureAlgorithm.HS256, "xxxx")// 设置过期时间.setExpiration(new Date(exp));String token = jwtBuilder.compact();System.out.println(token);}/*** 自定义Claims,在负载里存储更多的信息可以自定义*/@Testpublic void testCreateTokenByClaims() {long now = System.currentTimeMillis();// 设置过期时间,这里是1分钟后的时间长整型long exp = now + 60 * 1000;JwtBuilder jwtBuilder = Jwts.builder()//声明的标识{"jti":"888"}.setId("888")//主体,用户{"sub":"Rose"}.setSubject("Rose")//创建日期{"ita":"xxxxxx"}.setIssuedAt(new Date())//签名手段,参数1:算法,参数2:盐.signWith(SignatureAlgorithm.HS256, "xxxx")// 设置过期时间.setExpiration(new Date(exp))// 直接传入map也可以 .addClaims(map).claim("roles", "admin").claim("logo", "shsd.jpg");String token = jwtBuilder.compact();System.out.println(token);}/*** 解析token*/@Testpublic void testParseToken() {String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJSb3NlIiwiaWF0IjoxNjQ2MzYxMjIwLCJleHAiOjE2NDYzNjEyODAsInJvbGVzIjoiYWRtaW4iLCJsb2dvIjoic2hzZC5qcGcifQ.41yPcSw0Lp_AQ0b3nXGh513IUP8qs7pc_UJgym4olZ8";// 解析token获取负载中的声明对象Claims claims = Jwts.parser().setSigningKey("xxxx").parseClaimsJws(token).getBody();// 打印声明的属性System.out.println("id:"+claims.getId()); // 888System.out.println("subject:"+claims.getSubject()); // RoseSystem.out.println("issuedAt:"+claims.getIssuedAt()); // Fri Mar 04 10:22:04 CST 2022DateFormat 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"));}}
