JWT长的什么样
JWT是由三段信息构成的,将这三段信息文本用点.
链接一起就构成了Jwt字符串。就像这样:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTg0MjU5MzIsInVzZXJJZCI6MTExLCJ1c2VybmFtZSI6Ik1hcmtaUVAifQ.PTlOdRG7ROVJqPrA0q2ac7rKFzNNFR3lTMyP_8fIw9Q
JWT的构成
第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
header
jwt的头部承载两部分信息:
- 声明类型,这里是jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
payload
载荷就是存放有效信息的地方。有效载荷包含三个部分
- 标准注册声明
iss:jwt的签发者/发行人;
sub:主题;
aud:接收方;
exp:jwt过期时间;
nbf:jwt生效时间;
iat:签发时间
jti:jwt唯一身份标识,可以避免重放攻击 - 公共声明:
可以在公共声明添加任何信息,我们一般会在里面添加用户信息和业务信息,但是不建议添加敏感信息,因为公共声明部分可以在客户端解密。 - 私有声明:
私有声明是服务器和客户端共同定义的声明,同样这里不建议添加敏感信息。
如;我们一般在有效载荷里面放置;
{
"userId": 88782,
"username": "Mark",
"role": "admin"
}
signature
jwt的第三个部分是一个签证信息,这个签证信息由三部分组成
- header (base64后的)
- payload (base64后的)
- secret
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
用代码来生成一串token
新建一个Springboot项目
导入依赖
<?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.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jwttest</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>
<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>
<!--引入JWT得依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
直接在测试类上写token的生成与解析
package com.jwttest;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
class JwtApplicationTests {
@Test
void tokenProduces() {
Map<String, Object> map = new HashMap<>();
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7);
String token = JWT.create().withHeader(map) //生成token的header这一部分为令牌类型和所使用得签名算法,这个是默认的,可以不写
.withClaim("userId", 111) //生成token的payload部分,通常是放一些用户信息进去,如id,名字,权限等,千万不要放密码
.withClaim("username", "MarkZQP")
.withExpiresAt(instance.getTime()) //设token的过期时间
.sign(Algorithm.HMAC256("ksjfkjsakfjkajk")); //签名的算法及密钥,这里面的一串字符串是不能泄露的
System.out.println(token); //将token打印出来可以看出它是三段式的
}
@Test
void tokenVerify(){
//在验证过程中会报各种异常:1. 算法不一致 ---这个是最开始验证的
// 2. 签名不一致
// 3. token过期
// 4. 失效的payload
//创建验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("ksjfkjsakfjkajk")).build();
DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1OTg0MjU5MzIsInVzZXJJZCI6MTExLCJ1c2VybmFtZSI6Ik1hcmtaUVAifQ.PTlOdRG7ROVJqPrA0q2ac7rKFzNNFR3lTMyP_8fIw9Q");
//解出token里面传入的对象信息
System.out.println(verify.getClaim("userId").asInt());//因为传入的是int所以需要改成asInt()
System.out.println(verify.getClaim("username").asString());
System.out.println("过期时间"+verify.getExpiresAt()); //打印过期时间
}
}
** 代码中写了两个方法,一个是Token的生成,一个是Token的解析验证,在实际验证中,我们当然不是这样写,我们一般写成Utils工具类,这样更加方便使用**
JWTUtils工具类封装
封装工具类,将上面的代码,类似的封装进去,与上面不同的时,payload部分,我们将用户信息等通过参数来传递
package com.jwttest.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import jdk.internal.dynalink.beans.StaticClass;
import java.util.Calendar;
import java.util.Map;
import java.util.Stack;
public class JWTUtils {
private static final String SING = "ldjfklajsfjas";
/**
* 生成Token header.payload.sign
*/
public static String getToken(Map<String, String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE, 7); //默认7天过期
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));
return token;
}
/**
* 验证token的合法性
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(SING)).build().verify(token); //这一行代码就可以起到验证的作用,因为在验证不匹配时它自动会抛出异常
}
/**
* 获取token的信息方法
*/
public static DecodedJWT getTokenInfo(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
return verify;
}
}
在封装完JWT工具类后,在后面就直接可以与Springboot进行集成了。