文章目录
- JWT 介绍、token结构、示范代码与性能测试
- 介绍、token结构
- 示范
- 引入依赖
- 签发token
- 解析token
- 性能测试
- 签发性能测试
- 解析token性能测试
JWT 介绍、token结构、示范代码与性能测试
介绍、token结构
JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io
数据格式
JWT包含三部分数据:
- Header:头部,通常头部有两部分内容:
声明类型,这里是JWT
签名算法,自定义
我们会对头部进行base64加密(可解密),得到第一部分数据
- Payload:载荷,就是有效数据,一般包含下面信息:
用户身份信息(注意,这里因为采用base64加密,可直接解密,因此不要存放敏感信息)
tokenID:当前这个JWT的唯一标示
注册声明:如token的签发时间,过期时间,签发人等
这部分也会采用base64加密,得到第二部分数据
- Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性,建议设置30天有效期等
鉴权流程
示范
引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.3</version>
<scope>compile</scope>
</dependency>
签发token
private static final Algorithm algorithm = Algorithm.HMAC512("123sjdsfaf123eszc");
public static final String issuer = "test";
/**
* 签发一个jwt token
*/
@Test
public void testSignToken() {
JWTCreator.Builder builder = JWT.create();
// 设置token签发者
builder.withIssuer(issuer);
// 设置token 过期时间
builder.withExpiresAt(DateUtil.offsetDay(DateUtil.date(), 30));
// 声明一些自定义的属性(注意不要存太长的数据、敏感数据,因为这个东西设置之后,这个token任何人拿到无需密钥,直接通过base64解码中间那一段就能拿到这些信息)
builder.withClaim("userId", "123");
builder.withClaim("userName", "test");
builder.withClaim("userType", "1");
builder.withClaim("platform", "1");
builder.withClaim("phone", "11122223333");
// 签发token
String sign = builder.sign(algorithm);
log.info("jwt:{}", sign);
}
}
签发出的token
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJwaG9uZSI6IjExMTIyMjIzMzMzIiwiaXNzIjoidGVzdCIsInVzZXJUeXBlIjoiMSIsImV4cCI6MTcxODQzODE4NSwidXNlck5hbWUiOiJ0ZXN0IiwidXNlcklkIjoiMTIzIiwicGxhdGZvcm0iOiIxIn0.3JDY8JSkUj1C8rqOXwJugqoL3ZBDlCRtkDZZ-eXzbWSA4SFMZl-i-G8wP5lmPlWU9xqkIG64q1Aw54UMtQzwLg
解析token
通过JWTVerifier 来解析,解析后得到DecodedJWT ,可获取这个token的所有信息。如果token被修改过、过期都会验证失败并抛出对应异常
private static final Algorithm algorithm = Algorithm.HMAC512("123sjdsfaf123eszc");
public static final String issuer = "test";
/**
* 解析jwt和测试修改一下参数
*/
@Test
public void testDecodeJwtToken() {
// 创建解析器
JWTVerifier jwtVerifier = JWT.require(algorithm).withIssuer(issuer).build();
// 刚才生产的token
String sign = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJwaG9uZSI6IjExMTIyMjIzMzMzIiwiaXNzIjoidGVzdCIsInVzZXJUeXBlIjoiMSIsImV4cCI6MTcxODQzODE4NSwidXNlck5hbWUiOiJ0ZXN0IiwidXNlcklkIjoiMTIzIiwicGxhdGZvcm0iOiIxIn0.3JDY8JSkUj1C8rqOXwJugqoL3ZBDlCRtkDZZ-eXzbWSA4SFMZl-i-G8wP5lmPlWU9xqkIG64q1Aw54UMtQzwLg";
DecodedJWT decodedJWT = jwtVerifier.verify(sign);
String payload = decodedJWT.getPayload();
log.info("payload:{}", payload);
String decodeStr = Base64Decoder.decodeStr(payload);
log.info("decodeStr:{}", decodeStr);
String originHead = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9";
// 直接解码出来
log.info("originHead:{}", Base64Decoder.decodeStr(originHead));
// 直接截取token第二段
String originPayload = "eyJwaG9uZSI6IjExMTIyMjIzMzMzIiwiaXNzIjoidGVzdCIsInVzZXJUeXBlIjoiMSIsImV4cCI6MTcxODQzODE4NSwidXNlck5hbWUiOiJ0ZXN0IiwidXNlcklkIjoiMTIzIiwicGxhdGZvcm0iOiIxIn0";
// 直接解码出来
log.info("originPayload:{}", Base64Decoder.decodeStr(originPayload));
String originSign = "3JDY8JSkUj1C8rqOXwJugqoL3ZBDlCRtkDZZ-eXzbWSA4SFMZl-i-G8wP5lmPlWU9xqkIG64q1Aw54UMtQzwLg";
Date expiresAt = decodedJWT.getExpiresAt();
log.info("expiresAt:{}", DateUtil.formatDate(expiresAt));
// 改动一下token,比如改动一下userId
JSONObject jsonObject = JSONObject.parseObject(Base64Decoder.decodeStr(originPayload));
jsonObject.put("userId", "31221");
String modifiedToken = originHead + "." + Base64Encoder.encode(jsonObject.toJSONString()) + "." + originSign;
// 修改了之后的这个token就验证失败了 SignatureVerificationException
DecodedJWT verify = jwtVerifier.verify(modifiedToken);
log.info("verify:{}", Base64Decoder.decodeStr(verify.getPayload()));
}
验证时token过期的异常
com.auth0.jwt.exceptions.TokenExpiredException: The Token has expired on Thu May 16 16:07:39 CST 2024.
at com.auth0.jwt.JWTVerifier.assertDateIsFuture(JWTVerifier.java:472)
at com.auth0.jwt.JWTVerifier.assertValidDateClaim(JWTVerifier.java:463)
at com.auth0.jwt.JWTVerifier.verifyClaims(JWTVerifier.java:404)
at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:387)
at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:370)
token改动
com.auth0.jwt.exceptions.SignatureVerificationException: The Token's Signature resulted invalid when verified using the Algorithm: HmacSHA512
at com.auth0.jwt.algorithms.HMACAlgorithm.verify(HMACAlgorithm.java:50)
at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:386)
at com.auth0.jwt.JWTVerifier.verify(JWTVerifier.java:370)
at cn.sffix.customeruser.jwt.JwtPerformanceTest.testDecodeJwtToken(JwtPerformanceTest.java:83)
性能测试
运行设备信息
处理器 Intel® Core™ i7-9700 CPU @ 3.00GHz 3.00 GHz
机带 RAM 32.0 GB (31.9 GB 可用)
系统类型 64 位操作系统, 基于 x64 的处理器
签发性能测试
/**
* 签发token 性能测试
*/
@Test
public void testSignTokenPerformance() {
int count = 10000;
int j = count;
StopWatch stopWatch = new StopWatch();
stopWatch.start();
while (j > 0) {
JWTCreator.Builder builder = JWT.create();
builder.withIssuer(issuer);
builder.withIssuedAt(DateUtil.date());
builder.withExpiresAt(DateUtil.offsetDay(DateUtil.date(), 30));
builder.withClaim("userId", "123");
builder.withClaim("userName", "test");
builder.withClaim("userType", "1");
builder.withClaim("platform", "1");
builder.withClaim("phone", "11122223333");
String sign = builder.sign(algorithm);
j--;
}
stopWatch.stop();
BigDecimal avgCostTime = BigDecimal.valueOf(stopWatch.getTotalTimeMillis()).divide(BigDecimal.valueOf(count), 3, RoundingMode.HALF_UP);
log.info("执行 {} 次签发token耗时 {} ms 平均单词执行耗时 {} ms", count, stopWatch.getLastTaskTimeMillis(), avgCostTime);
}
执行结果
16:04:37.045 [main] INFO cn.sffix.customeruser.jwt.JwtPerformanceTest - 执行 10000 次签发token耗时 1660 ms 平均单词执行耗时 0.166 ms
解析token性能测试
/**
* 测试解析性能
*/
@Test
public void testVerifyTokenPerformance() {
String sign = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJwaG9uZSI6IjExMTIyMjIzMzMzIiwiaXNzIjoidGVzdCIsInVzZXJUeXBlIjoiMSIsImV4cCI6MTcxODQzMzkwNSwidXNlck5hbWUiOiJ0ZXN0IiwiaWF0IjoxNzE1ODQxOTA1LCJ1c2VySWQiOiIxMjMiLCJwbGF0Zm9ybSI6IjEifQ.tCygoRcQaay-cYt_ug0QxPXrF8GJ2Er8L7NPVHjxsbxD0elWRZzHmUHzgFT3SBXXB7fX6xanCqmMy0k5cw_ovA";
JWTVerifier jwtVerifier = JWT.require(algorithm).withIssuer(issuer).build();
int count = 10000;
int j = count;
StopWatch stopWatch = new StopWatch();
stopWatch.start();
while (j > 0) {
DecodedJWT verify = jwtVerifier.verify(sign);
j--;
}
stopWatch.stop();
BigDecimal avgCostTime = BigDecimal.valueOf(stopWatch.getTotalTimeMillis()).divide(BigDecimal.valueOf(count), 3, RoundingMode.HALF_UP);
log.info("执行 {} 次解析token耗时 {} ms 平均每次耗时 {} ms", count, stopWatch.getLastTaskTimeMillis(), avgCostTime);
}
16:05:29.370 [main] INFO cn.sffix.customeruser.jwt.JwtPerformanceTest - 执行 10000 次解析token耗时 1122 ms 平均每次耗时 0.112 ms