https://www.hoshino.asia/archives/hutool

概述

由来

从5.7.0开始,Hutool提供了零依赖的JWT(JSON Web Token)实现。

JWT介绍

相关资料网络上非常多,可以自行搜索,简单点说JWT就是一种网络身份认证和信息交换格式。

结构

  • Header 头部信息,主要声明了JWT的签名算法等信息

  • Payload 载荷信息,主要承载了各种声明并传递明文数据

  • Signature 签名,拥有该部分的JWT被称为JWS,也就是签了名的JWT,用于校验数据

整体结构是:

header.payload.signature

使用

JWT模块的核心主要是两个类:

  1. JWT类用于链式生成、解析或验证JWT信息。

  2. JWTUtil类主要是JWT的一些工具封装,提供更加简洁的JWT生成、解析和验证工作

JWT生成

  1. HS256(HmacSHA256)算法

// 密钥
byte[] key = "1234567890".getBytes();

String token = JWT.create()
	.setPayload("sub", "1234567890")
	.setPayload("name", "looly")
	.setPayload("admin", true)
	.setKey(key)
	.sign();

生成的内容为:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9.536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40
  1. 其他算法

// 密钥
byte[] key = "1234567890".getBytes();

// SHA256withRSA
String id = "rs256";
JWTSigner signer = JWTSignerUtil.createSigner(id, 
	// 随机生成密钥对,此处用户可自行读取`KeyPair`、公钥或私钥生成`JWTSigner`
	KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id)));

String token = JWT.create()
	.setPayload("sub", "1234567890")
	.setPayload("name", "looly")
	.setPayload("admin", true)
	.setSigner(signer)
	.sign();
  1. 不签名JWT

// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9.
String token = JWT.create()
	.setPayload("sub", "1234567890")
	.setPayload("name", "looly")
	.setPayload("admin", true)
	.setSigner(JWTSignerUtil.none())
	.sign()

JWT解析

String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
	"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
	"536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40";

JWT jwt = JWT.of(rightToken);

// JWT
jwt.getHeader(JWTHeader.TYPE);
// HS256
jwt.getHeader(JWTHeader.ALGORITHM);

// 1234567890
jwt.getPayload("sub");
// looly
jwt.getPayload("name");
// true
jwt.getPayload("admin");

JWT验证

  1. 验证签名

String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
	"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
	"536690902d931d857d2f47d337ec81048ee09a8e71866bcc8404edbbcbf4cc40";

// 密钥
byte[] key = "1234567890".getBytes();

// 默认验证HS256的算法
JWT.of(rightToken).setKey(key).verify()
  1. 详细验证

除了验证签名,Hutool提供了更加详细的验证:validate,主要包括:

  • Token是否正确

  • 生效时间不能晚于当前时间

  • 失效时间不能早于当前时间

  • 签发时间不能晚于当前时间

使用方式如下:

String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNb0xpIiwiZXhwIjoxNjI0OTU4MDk0NTI4LCJpYXQiOjE2MjQ5NTgwMzQ1MjAsInVzZXIiOiJ1c2VyIn0.L0uB38p9sZrivbmP0VlDe--j_11YUXTu3TfHhfQhRKc";

byte[] key = "1234567890".getBytes();
// 容忍时间,0秒
boolean validate = JWT.of(token).setKey(key).validate(0);

其他自定义详细验证见JWT验证-JWTValidator章节。

JWT工具-JWTUtil

介绍

我们可以通过JWT实现链式创建JWT对象或JWT字符串,Hutool同样提供了一些快捷方法封装在JWTUtil中。主要包括:

  • JWT创建

  • JWT解析

  • JWT验证

使用

  • JWT创建

Map<String, Object> map = new HashMap<String, Object>() {
	private static final long serialVersionUID = 1L;
	{
		put("uid", Integer.parseInt("123"));
		put("expire_time", System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 15);
	}
};

JWTUtil.createToken(map, "1234".getBytes());
  • JWT解析

String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
	"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
	"U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";

final JWT jwt = JWTUtil.parseToken(rightToken);

jwt.getHeader(JWTHeader.TYPE);
jwt.getPayload("sub");
  • JWT验证

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
	"eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ." +
	"aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";

JWTUtil.verify(token, "123456".getBytes());

JWT签名工具-JWTSignerUtil

介绍

JWT签名算法比较多,主要分为非对称算法和对称算法,支持的算法定义在SignAlgorithm中。

对称签名

  • HS256(HmacSHA256)

  • HS384(HmacSHA384)

  • HS512(HmacSHA512)

非对称签名

  • RS256(SHA256withRSA)

  • RS384(SHA384withRSA)

  • RS512(SHA512withRSA)

  • ES256(SHA256withECDSA)

  • ES384(SHA384withECDSA)

  • ES512(SHA512withECDSA)

依赖于BounyCastle的算法

  • PS256(SHA256WithRSA/PSS)

  • PS384(SHA384WithRSA/PSS)

  • PS512(SHA512WithRSA/PSS)

使用

创建预定义算法签名器

JWTSignerUtil中预定义了一些算法的签名器的创建方法,如创建HS256的签名器:

final JWTSigner signer = JWTSignerUtil.hs256("123456".getBytes());
JWT jwt = JWT.create().setSigner(signer);

创建自定义算法签名器

通过JWTSignerUtil.createSigner即可通过动态传入algorithmId创建对应的签名器,如我们如果需要实现ps256算法,则首先引入bcprov-jdk15to18包:

<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcpkix-jdk18on</artifactId>
	<version>${bouncycastle.version}</version>
</dependency>

再创建对应签名器即可:

String id = "ps256";
final JWTSigner signer = JWTSignerUtil.createSigner(id, KeyUtil.generateKeyPair(AlgorithmUtil.getAlgorithm(id)));

JWT jwt = JWT.create().setSigner(signer);

自行实现算法签名器

JWTSigner接口是一个通用的签名器接口,如果想实现自定义算法,实现此接口即可。

JWT验证-JWTValidator

介绍

由于JWT.verify,只能验证JWT Token的签名是否有效,其他payload字段验证都可以使用JWTValidator完成。

使用

验证算法

算法的验证包括两个方面

  1. 验证header中算法ID和提供的算法ID是否一致

  2. 调用JWT.verify验证token是否正确

// 创建JWT Token
final String token = JWT.create()
	.setNotBefore(DateUtil.date())
	.setKey("123456".getBytes())
	.sign();

// 验证算法
JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256("123456".getBytes()));

验证时间

对于时间类载荷,有单独的验证方法,主要包括:

  • 生效时间(JWTPayload.NOT_BEFORE)不能晚于当前时间

  • 失效时间(JWTPayload.EXPIRES_AT)不能早于当前时间

  • 签发时间(JWTPayload.ISSUED_AT)不能晚于当前时间

一般时间线是:

(签发时间)---------(生效时间)---------(当前时间)---------(失效时间)

签发时间和生效时间一般没有前后要求,都早于当前时间即可。

final String token = JWT.create()
	// 设置签发时间
	.setIssuedAt(DateUtil.date())
	.setKey("123456".getBytes())
	.sign();

// 由于只定义了签发时间,因此只检查签发时间
JWTValidator.of(token).validateDate(DateUtil.date());

Ciallo~(∠・ω< )⌒☆