mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
add JWTValidator
This commit is contained in:
parent
a844a81782
commit
59275491de
@ -3,7 +3,7 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.7.2 (2021-06-18)
|
||||
# 5.7.2 (2021-06-19)
|
||||
|
||||
### 🐣新特性
|
||||
* 【core 】 增加UserPassAuthenticator
|
||||
@ -18,6 +18,7 @@
|
||||
* 【core 】 修复ConcurrencyTester重复使用时开始测试未清空之前任务的问题(issue#I3VSDO@Gitee)
|
||||
* 【poi 】 修复使用BigWriter写出,ExcelWriter修改单元格值失败的问题(issue#I3VSDO@Gitee)
|
||||
* 【jwt 】 修复Hmac算法下生成签名是hex的问题(issue#I3W6IP@Gitee)
|
||||
* 【jwt 】 修复TreeUtil.build中deep失效问题(issue#1661@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -23,6 +23,11 @@ import cn.hutool.core.util.StrUtil;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 秋千水,竹马道,一眼见你,万物不及。<br>
|
||||
* 春水生,春林初胜,春风十里不如你。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
* </p>
|
||||
|
@ -118,6 +118,9 @@ public class ConverterRegistry implements Serializable {
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public ConverterRegistry() {
|
||||
defaultConverter();
|
||||
putCustomBySpi();
|
||||
|
@ -724,8 +724,8 @@ public class DateTime extends Date {
|
||||
* 当前日期是否在日期指定范围内<br>
|
||||
* 起始日期和结束日期可以互换
|
||||
*
|
||||
* @param beginDate 起始日期
|
||||
* @param endDate 结束日期
|
||||
* @param beginDate 起始日期(包含)
|
||||
* @param endDate 结束日期(包含)
|
||||
* @return 是否在范围内
|
||||
* @since 3.0.8
|
||||
*/
|
||||
|
@ -1464,8 +1464,8 @@ public class DateUtil extends CalendarUtil {
|
||||
* 起始日期和结束日期可以互换
|
||||
*
|
||||
* @param date 被检查的日期
|
||||
* @param beginDate 起始日期
|
||||
* @param endDate 结束日期
|
||||
* @param beginDate 起始日期(包含)
|
||||
* @param endDate 结束日期(包含)
|
||||
* @return 是否在范围内
|
||||
* @since 3.0.8
|
||||
*/
|
||||
|
@ -88,7 +88,7 @@ public class PatternPool {
|
||||
*
|
||||
* @see <a href="https://baike.baidu.com/item/800">800</a>
|
||||
*/
|
||||
public final static Pattern TEL_400_800 = Pattern.compile("(?:(?:0\\d{2,3}[\\- ]?[1-9]\\d{6,7})|(?:[48]00[\\- ]?[1-9]\\d{6}))");
|
||||
public final static Pattern TEL_400_800 = Pattern.compile("0\\d{2,3}[\\- ]?[1-9]\\d{6,7}|[48]00[\\- ]?[1-9]\\d{6}");
|
||||
/**
|
||||
* 18位身份证号码
|
||||
*/
|
||||
@ -124,7 +124,7 @@ public class PatternPool {
|
||||
/**
|
||||
* MAC地址正则
|
||||
*/
|
||||
public static final Pattern MAC_ADDRESS = Pattern.compile("((?:[A-F0-9]{1,2}[:-]){5}[A-F0-9]{1,2})|(?:0x)(\\d{12})(?:.+ETHER)", Pattern.CASE_INSENSITIVE);
|
||||
public static final Pattern MAC_ADDRESS = Pattern.compile("((?:[A-F0-9]{1,2}[:-]){5}[A-F0-9]{1,2})|0x(\\d{12}).+ETHER", Pattern.CASE_INSENSITIVE);
|
||||
/**
|
||||
* 16进制字符串
|
||||
*/
|
||||
|
@ -14,7 +14,14 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 字段验证器
|
||||
* 字段验证器(验证器),分两种类型的验证:
|
||||
*
|
||||
* <ul>
|
||||
* <li>isXXX:通过返回boolean值判断是否满足给定格式。</li>
|
||||
* <li>validateXXX:通过抛出异常{@link ValidateException}检查是否满足给定格式。</li>
|
||||
* </ul>
|
||||
*
|
||||
* 主要验证字段非空、是否为满足指定格式等(如是否为Email、电话等)
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
|
@ -164,11 +164,22 @@ public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有子节点
|
||||
*
|
||||
* @return 所有子节点
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Tree<T>> getChildren() {
|
||||
return (List<Tree<T>>) this.get(treeNodeConfig.getChildrenKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子节点,设置后会覆盖所有原有子节点
|
||||
*
|
||||
* @param children 子节点列表
|
||||
* @return this
|
||||
*/
|
||||
public Tree<T> setChildren(List<Tree<T>> children) {
|
||||
this.put(treeNodeConfig.getChildrenKey(), children);
|
||||
return this;
|
||||
|
@ -2,6 +2,7 @@ package cn.hutool.core.lang.tree;
|
||||
|
||||
import cn.hutool.core.builder.Builder;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.tree.parser.NodeParser;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@ -21,6 +22,7 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
|
||||
private final Tree<E> root;
|
||||
private final Map<E, Tree<E>> idTreeMap;
|
||||
private boolean isBuild;
|
||||
|
||||
/**
|
||||
* 创建Tree构建器
|
||||
@ -64,6 +66,9 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
* @return this
|
||||
*/
|
||||
public TreeBuilder<E> append(Map<E, Tree<E>> map) {
|
||||
checkBuilt();
|
||||
|
||||
Assert.isFalse(isBuild, "Current tree has been built.");
|
||||
this.idTreeMap.putAll(map);
|
||||
return this;
|
||||
}
|
||||
@ -75,6 +80,8 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
* @return this
|
||||
*/
|
||||
public TreeBuilder<E> append(Iterable<Tree<E>> trees) {
|
||||
checkBuilt();
|
||||
|
||||
for (Tree<E> tree : trees) {
|
||||
this.idTreeMap.put(tree.getId(), tree);
|
||||
}
|
||||
@ -90,6 +97,8 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
* @return this
|
||||
*/
|
||||
public <T> TreeBuilder<E> append(List<T> list, NodeParser<T, E> nodeParser) {
|
||||
checkBuilt();
|
||||
|
||||
final TreeNodeConfig config = this.root.getConfig();
|
||||
final Map<E, Tree<E>> map = new LinkedHashMap<>(list.size(), 1);
|
||||
Tree<E> node;
|
||||
@ -101,38 +110,69 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
return append(map);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重置Builder,实现复用
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public TreeBuilder<E> reset() {
|
||||
this.idTreeMap.clear();
|
||||
this.root.setChildren(null);
|
||||
this.isBuild = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree<E> build() {
|
||||
checkBuilt();
|
||||
|
||||
buildFromMap();
|
||||
cutTree();
|
||||
|
||||
this.isBuild = true;
|
||||
this.idTreeMap.clear();
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建树列表,例如:
|
||||
* 构建树列表,没有顶层节点,例如:
|
||||
*
|
||||
* <pre>
|
||||
* -用户管理
|
||||
* --用户添加
|
||||
* --用户管理
|
||||
* -用户管理
|
||||
* +用户添加
|
||||
* - 部门管理
|
||||
* --部门添加
|
||||
* --部门管理
|
||||
* -部门管理
|
||||
* +部门添加
|
||||
* </pre>
|
||||
*
|
||||
* @return 树列表
|
||||
*/
|
||||
public List<Tree<E>> buildList() {
|
||||
return this.root.getChildren();
|
||||
if (isBuild) {
|
||||
// 已经构建过了
|
||||
return this.root.getChildren();
|
||||
}
|
||||
return build().getChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始构建
|
||||
*/
|
||||
private void buildFromMap() {
|
||||
if (MapUtil.isEmpty(this.idTreeMap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<E, Tree<E>> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false);
|
||||
List<Tree<E>> rootTreeList = CollUtil.newArrayList();
|
||||
E parentId;
|
||||
for (Tree<E> node : eTreeMap.values()) {
|
||||
if (null == node) {
|
||||
continue;
|
||||
}
|
||||
parentId = node.getParentId();
|
||||
if (ObjectUtil.equals(this.root.getId(), parentId)) {
|
||||
this.root.addChildren(node);
|
||||
@ -146,4 +186,48 @@ public class TreeBuilder<E> implements Builder<Tree<E>> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树剪枝
|
||||
*/
|
||||
private void cutTree() {
|
||||
final TreeNodeConfig config = this.root.getConfig();
|
||||
final Integer deep = config.getDeep();
|
||||
if (null == deep || deep < 0) {
|
||||
return;
|
||||
}
|
||||
cutTree(this.root, 0, deep);
|
||||
}
|
||||
|
||||
/**
|
||||
* 树剪枝叶
|
||||
*
|
||||
* @param tree 节点
|
||||
* @param currentDepp 当前层级
|
||||
* @param maxDeep 最大层级
|
||||
*/
|
||||
private void cutTree(Tree<E> tree, int currentDepp, int maxDeep) {
|
||||
if (null == tree) {
|
||||
return;
|
||||
}
|
||||
if (currentDepp == maxDeep) {
|
||||
// 剪枝
|
||||
tree.setChildren(null);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Tree<E>> children = tree.getChildren();
|
||||
if (CollUtil.isNotEmpty(children)) {
|
||||
for (Tree<E> child : children) {
|
||||
cutTree(child, currentDepp + 1, maxDeep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已经构建
|
||||
*/
|
||||
private void checkBuilt() {
|
||||
Assert.isFalse(isBuild, "Current tree has been built.");
|
||||
}
|
||||
}
|
||||
|
@ -134,11 +134,11 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
Number value = values[0];
|
||||
BigDecimal result = null == value ? BigDecimal.ZERO : new BigDecimal(value.toString());
|
||||
BigDecimal result = toBigDecimal(value);
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
value = values[i];
|
||||
if (null != value) {
|
||||
result = result.add(new BigDecimal(value.toString()));
|
||||
result = result.add(toBigDecimal(value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -158,11 +158,11 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
String value = values[0];
|
||||
BigDecimal result = StrUtil.isBlank(value) ? BigDecimal.ZERO : new BigDecimal(value);
|
||||
BigDecimal result = toBigDecimal(value);
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
value = values[i];
|
||||
if (StrUtil.isNotBlank(value)) {
|
||||
result = result.add(new BigDecimal(value));
|
||||
result = result.add(toBigDecimal(value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -182,7 +182,7 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
BigDecimal value = values[0];
|
||||
BigDecimal result = null == value ? BigDecimal.ZERO : value;
|
||||
BigDecimal result = toBigDecimal(value);
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
value = values[i];
|
||||
if (null != value) {
|
||||
@ -274,11 +274,11 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
Number value = values[0];
|
||||
BigDecimal result = null == value ? BigDecimal.ZERO : new BigDecimal(value.toString());
|
||||
BigDecimal result = toBigDecimal(value);
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
value = values[i];
|
||||
if (null != value) {
|
||||
result = result.subtract(new BigDecimal(value.toString()));
|
||||
result = result.subtract(toBigDecimal(value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -298,11 +298,11 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
String value = values[0];
|
||||
BigDecimal result = StrUtil.isBlank(value) ? BigDecimal.ZERO : new BigDecimal(value);
|
||||
BigDecimal result = toBigDecimal(value);
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
value = values[i];
|
||||
if (StrUtil.isNotBlank(value)) {
|
||||
result = result.subtract(new BigDecimal(value));
|
||||
result = result.subtract(toBigDecimal(value));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -322,7 +322,7 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
BigDecimal value = values[0];
|
||||
BigDecimal result = null == value ? BigDecimal.ZERO : value;
|
||||
BigDecimal result = toBigDecimal(value);
|
||||
for (int i = 1; i < values.length; i++) {
|
||||
value = values[i];
|
||||
if (null != value) {
|
||||
@ -729,9 +729,7 @@ public class NumberUtil {
|
||||
* @return 两个参数的商
|
||||
*/
|
||||
public static BigDecimal div(String v1, String v2, int scale, RoundingMode roundingMode) {
|
||||
final BigDecimal bd1 = StrUtil.isBlank(v1) ? BigDecimal.ZERO : new BigDecimal(v1);
|
||||
final BigDecimal bd2 = StrUtil.isBlank(v2) ? BigDecimal.ZERO : new BigDecimal(v2);
|
||||
return div(bd1, bd2, scale, roundingMode);
|
||||
return div(toBigDecimal(v1), toBigDecimal(v2), scale, roundingMode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,16 @@
|
||||
package cn.hutool.core.lang.tree;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class TreeBuilderTest {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void checkIsBuiltTest(){
|
||||
final TreeBuilder<Integer> of = TreeBuilder.of(0);
|
||||
of.build();
|
||||
of.append(new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
@ -50,7 +50,7 @@ public class TreeTest {
|
||||
// 自定义属性名 都要默认值的
|
||||
treeNodeConfig.setWeightKey("order");
|
||||
treeNodeConfig.setIdKey("rid");
|
||||
treeNodeConfig.setDeep(3);
|
||||
treeNodeConfig.setDeep(2);
|
||||
|
||||
//转换器
|
||||
List<Tree<String>> treeNodes = TreeUtil.build(nodeList, "0", treeNodeConfig,
|
||||
@ -66,4 +66,5 @@ public class TreeTest {
|
||||
|
||||
Assert.assertEquals(treeNodes.size(), 2);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,6 +48,12 @@ public class NumberUtilTest {
|
||||
Assert.assertEquals(new BigDecimal("464"), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addBlankTest(){
|
||||
BigDecimal result = NumberUtil.add("123", " ");
|
||||
Assert.assertEquals(new BigDecimal("123"), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isIntegerTest() {
|
||||
Assert.assertTrue(NumberUtil.isInteger("-12"));
|
||||
|
@ -10,6 +10,7 @@ import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.jwt.signers.AlgorithmUtil;
|
||||
import cn.hutool.jwt.signers.JWTSigner;
|
||||
import cn.hutool.jwt.signers.JWTSignerUtil;
|
||||
import cn.hutool.jwt.signers.NoneJWTSigner;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Key;
|
||||
@ -20,11 +21,11 @@ import java.util.Map;
|
||||
/**
|
||||
* JSON Web Token (JWT),基于JSON的开放标准((RFC 7519)用于在网络应用环境间传递声明。<br>
|
||||
* <p>
|
||||
* 结构:xxxxx.yyyyy.zzzzz
|
||||
* 结构:header.payload.signature
|
||||
* <ul>
|
||||
* <li>header:主要声明了JWT的签名算法</li>
|
||||
* <li>payload:主要承载了各种声明并传递明文数据</li>
|
||||
* <li>signture:拥有该部分的JWT被称为JWS,也就是签了名的JWS</li>
|
||||
* <li>signature:拥有该部分的JWT被称为JWS,也就是签了名的JWS</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
@ -32,8 +33,9 @@ import java.util.Map;
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public class JWT {
|
||||
public class JWT implements RegisteredPayload<JWT>{
|
||||
|
||||
private final JWTHeader header;
|
||||
private final JWTPayload payload;
|
||||
@ -162,6 +164,15 @@ public class JWT {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JWT算法签名器
|
||||
*
|
||||
* @return JWT算法签名器
|
||||
*/
|
||||
public JWTSigner getSigner(){
|
||||
return this.signer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有头信息
|
||||
*
|
||||
@ -171,6 +182,16 @@ public class JWT {
|
||||
return this.header.getClaimsJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头
|
||||
*
|
||||
* @return 头信息
|
||||
* @since 5.7.2
|
||||
*/
|
||||
public JWTHeader getHeader() {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取头信息
|
||||
*
|
||||
@ -223,6 +244,16 @@ public class JWT {
|
||||
return this.payload.getClaimsJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取载荷对象
|
||||
*
|
||||
* @return 载荷信息
|
||||
* @since 5.7.2
|
||||
*/
|
||||
public JWTPayload getPayload() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取载荷信息
|
||||
*
|
||||
@ -230,7 +261,7 @@ public class JWT {
|
||||
* @return 载荷信息
|
||||
*/
|
||||
public Object getPayload(String name) {
|
||||
return this.payload.getClaim(name);
|
||||
return getPayload().getClaim(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,6 +271,7 @@ public class JWT {
|
||||
* @param value 头
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public JWT setPayload(String name, Object value) {
|
||||
this.payload.setClaim(name, value);
|
||||
return this;
|
||||
@ -304,7 +336,10 @@ public class JWT {
|
||||
* @return 是否有效
|
||||
*/
|
||||
public boolean verify(JWTSigner signer) {
|
||||
Assert.notNull(signer, () -> new JWTException("No Signer provided!"));
|
||||
if(null == signer){
|
||||
// 如果无签名器提供,默认认为是无签名JWT信息
|
||||
signer = NoneJWTSigner.NONE;
|
||||
}
|
||||
|
||||
final List<String> tokens = this.tokens;
|
||||
if (CollUtil.isEmpty(tokens)) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.jwt;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -18,117 +17,9 @@ import java.util.Map;
|
||||
* @author looly
|
||||
* @since 5.7.0
|
||||
*/
|
||||
public class JWTPayload extends Claims {
|
||||
public class JWTPayload extends Claims implements RegisteredPayload<JWTPayload>{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* jwt签发者
|
||||
*/
|
||||
public static String ISSUER = "iss";
|
||||
/**
|
||||
* jwt所面向的用户
|
||||
*/
|
||||
public static String SUBJECT = "sub";
|
||||
/**
|
||||
* 接收jwt的一方
|
||||
*/
|
||||
public static String AUDIENCE = "aud";
|
||||
/**
|
||||
* jwt的过期时间,这个过期时间必须要大于签发时间
|
||||
*/
|
||||
public static String EXPIRES_AT = "exp";
|
||||
/**
|
||||
* 定义在什么时间之前,该jwt都是不可用的.
|
||||
*/
|
||||
public static String NOT_BEFORE = "nbf";
|
||||
/**
|
||||
* jwt的签发时间
|
||||
*/
|
||||
public static String ISSUED_AT = "iat";
|
||||
/**
|
||||
* jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
|
||||
*/
|
||||
public static String JWT_ID = "jti";
|
||||
|
||||
/**
|
||||
* 设置 jwt签发者("iss")的Payload值
|
||||
*
|
||||
* @param issuer jwt签发者
|
||||
* @return this
|
||||
*/
|
||||
public JWTPayload setIssuer(String issuer) {
|
||||
setClaim(ISSUER, issuer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt所面向的用户("sub")的Payload值
|
||||
*
|
||||
* @param subject jwt所面向的用户
|
||||
* @return this
|
||||
*/
|
||||
public JWTPayload setSubject(String subject) {
|
||||
setClaim(SUBJECT, subject);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置接收jwt的一方("aud")的Payload值
|
||||
*
|
||||
* @param audience 接收jwt的一方
|
||||
* @return this
|
||||
*/
|
||||
public JWTPayload setAudience(String... audience) {
|
||||
setClaim(AUDIENCE, audience);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a specific Expires At ("exp") claim to the Payload.
|
||||
* 设置jwt的过期时间("exp")的Payload值,这个过期时间必须要大于签发时间
|
||||
*
|
||||
* @param expiresAt jwt的过期时间
|
||||
* @return this
|
||||
* @see #setIssuedAt(Date)
|
||||
*/
|
||||
public JWTPayload setExpiresAt(Date expiresAt) {
|
||||
setClaim(EXPIRES_AT, expiresAt);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置不可用时间点界限("nbf")的Payload值
|
||||
*
|
||||
* @param notBefore 不可用时间点界限,在这个时间点之前,jwt不可用
|
||||
* @return this
|
||||
*/
|
||||
public JWTPayload setNotBefore(Date notBefore) {
|
||||
setClaim(NOT_BEFORE, notBefore);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt的签发时间("iat")
|
||||
*
|
||||
* @param issuedAt 签发时间
|
||||
* @return this
|
||||
*/
|
||||
public JWTPayload setIssuedAt(Date issuedAt) {
|
||||
setClaim(ISSUED_AT, issuedAt);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt的唯一身份标识("jti")
|
||||
*
|
||||
* @param jwtId 唯一身份标识
|
||||
* @return this
|
||||
*/
|
||||
public JWTPayload setJWTId(String jwtId) {
|
||||
setClaim(JWT_ID, jwtId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加自定义JWT认证载荷信息
|
||||
*
|
||||
@ -139,4 +30,10 @@ public class JWTPayload extends Claims {
|
||||
putAll(payloadClaims);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JWTPayload setPayload(String name, Object value) {
|
||||
setClaim(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
176
hutool-jwt/src/main/java/cn/hutool/jwt/JWTValidator.java
Normal file
176
hutool-jwt/src/main/java/cn/hutool/jwt/JWTValidator.java
Normal file
@ -0,0 +1,176 @@
|
||||
package cn.hutool.jwt;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.exceptions.ValidateException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.jwt.signers.JWTSigner;
|
||||
import cn.hutool.jwt.signers.NoneJWTSigner;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT数据校验器,用于校验包括:
|
||||
* <ul>
|
||||
* <li>算法是否一致</li>
|
||||
* <li>算法签名是否正确</li>
|
||||
* <li>字段值是否有效(例如时间未过期等)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.2
|
||||
*/
|
||||
public class JWTValidator {
|
||||
|
||||
private final JWT jwt;
|
||||
|
||||
/**
|
||||
* 创建JWT验证器
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return {@link JWTValidator}
|
||||
*/
|
||||
public static JWTValidator of(String token) {
|
||||
return new JWTValidator(JWT.of(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建JWT验证器
|
||||
*
|
||||
* @param jwt JWT对象
|
||||
* @return {@link JWTValidator}
|
||||
*/
|
||||
public static JWTValidator of(JWT jwt) {
|
||||
return new JWTValidator(jwt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param jwt JWT对象
|
||||
*/
|
||||
public JWTValidator(JWT jwt) {
|
||||
this.jwt = jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证算法,使用JWT对象自带的{@link JWTSigner}
|
||||
*
|
||||
* @return this
|
||||
* @throws ValidateException 验证失败的异常
|
||||
*/
|
||||
public JWTValidator validateAlgorithm() throws ValidateException {
|
||||
return validateAlgorithm(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证算法,使用自定义的{@link JWTSigner}
|
||||
*
|
||||
* @param signer 用于验证算法的签名器
|
||||
* @return this
|
||||
* @throws ValidateException 验证失败的异常
|
||||
*/
|
||||
public JWTValidator validateAlgorithm(JWTSigner signer) throws ValidateException {
|
||||
validateAlgorithm(this.jwt, signer);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查JWT的以下三两个时间:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link JWTPayload#NOT_BEFORE}:被检查时间必须晚于生效时间</li>
|
||||
* <li>{@link JWTPayload#EXPIRES_AT}:被检查时间必须早于失效时间</li>
|
||||
* <li>{@link JWTPayload#ISSUED_AT}:签发时间必须早于失效时间</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 如果某个时间没有设置,则不检查(表示无限制)
|
||||
*
|
||||
* @param dateToCheck 被检查的时间,一般为当前时间
|
||||
* @return this
|
||||
* @throws ValidateException 验证失败的异常
|
||||
*/
|
||||
public JWTValidator validateDate(Date dateToCheck) throws ValidateException {
|
||||
validateDate(this.jwt.getPayload(), dateToCheck);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证算法
|
||||
*
|
||||
* @param jwt {@link JWT}对象
|
||||
* @param signer 用于验证的签名器
|
||||
* @throws ValidateException 验证异常
|
||||
*/
|
||||
private static void validateAlgorithm(JWT jwt, JWTSigner signer) throws ValidateException {
|
||||
final String algorithmId = jwt.getAlgorithm();
|
||||
if (null == signer) {
|
||||
signer = jwt.getSigner();
|
||||
}
|
||||
|
||||
if (StrUtil.isEmpty(algorithmId)) {
|
||||
// 可能无签名
|
||||
if (null == signer || signer instanceof NoneJWTSigner) {
|
||||
return;
|
||||
}
|
||||
throw new ValidateException("No algorithm defined in header!");
|
||||
}
|
||||
|
||||
if (null == signer) {
|
||||
throw new IllegalArgumentException("No Signer for validate algorithm!");
|
||||
}
|
||||
|
||||
final String algorithmIdInSigner = signer.getAlgorithmId();
|
||||
if (false == StrUtil.equals(algorithmId, algorithmIdInSigner)) {
|
||||
throw new ValidateException("Algorithm [{}] defined in header doesn't match to [{}]!"
|
||||
, algorithmId, algorithmIdInSigner);
|
||||
}
|
||||
|
||||
// 通过算法验证签名是否正确
|
||||
if (false == jwt.verify(signer)) {
|
||||
throw new ValidateException("Signature verification failed!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查JWT的以下三两个时间:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link JWTPayload#NOT_BEFORE}:被检查时间必须晚于生效时间</li>
|
||||
* <li>{@link JWTPayload#EXPIRES_AT}:被检查时间必须早于失效时间</li>
|
||||
* <li>{@link JWTPayload#ISSUED_AT}:签发时间必须早于失效时间</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 如果某个时间没有设置,则不检查(表示无限制)
|
||||
*
|
||||
* @param payload {@link JWTPayload}
|
||||
* @param dateToCheck 被检查的时间,一般为当前时间
|
||||
* @throws ValidateException 验证异常
|
||||
*/
|
||||
private static void validateDate(JWTPayload payload, Date dateToCheck) throws ValidateException {
|
||||
if (null == dateToCheck) {
|
||||
// 默认当前时间
|
||||
dateToCheck = DateUtil.date();
|
||||
}
|
||||
|
||||
// 检查生效时间(被检查时间必须晚于生效时间)
|
||||
final Date notBefore = payload.getClaimsJson().getDate(JWTPayload.NOT_BEFORE);
|
||||
if (null != notBefore && dateToCheck.before(notBefore)) {
|
||||
throw new ValidateException("Current date [{}] is before 'nbf' [{}]",
|
||||
dateToCheck, DateUtil.date(notBefore));
|
||||
}
|
||||
|
||||
// 检查失效时间(被检查时间必须早于失效时间)
|
||||
final Date expiresAt = payload.getClaimsJson().getDate(JWTPayload.EXPIRES_AT);
|
||||
if (null != expiresAt && dateToCheck.after(expiresAt)) {
|
||||
throw new ValidateException("Current date [{}] is after 'exp' [{}]",
|
||||
dateToCheck, DateUtil.date(expiresAt));
|
||||
}
|
||||
|
||||
// 检查签发时间(被检查时间必须晚于签发时间)
|
||||
final Date issueAt = payload.getClaimsJson().getDate(JWTPayload.ISSUED_AT);
|
||||
if (null != issueAt && dateToCheck.before(issueAt)) {
|
||||
throw new ValidateException("Current date [{}] is before 'iat' [{}]",
|
||||
dateToCheck, DateUtil.date(issueAt));
|
||||
}
|
||||
}
|
||||
}
|
122
hutool-jwt/src/main/java/cn/hutool/jwt/RegisteredPayload.java
Normal file
122
hutool-jwt/src/main/java/cn/hutool/jwt/RegisteredPayload.java
Normal file
@ -0,0 +1,122 @@
|
||||
package cn.hutool.jwt;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 注册的标准载荷(Payload)声明
|
||||
*
|
||||
* @param <T> 实现此接口的类的类型
|
||||
* @author looly
|
||||
* @since 5.7.2
|
||||
*/
|
||||
public interface RegisteredPayload<T extends RegisteredPayload<T>> {
|
||||
|
||||
/**
|
||||
* jwt签发者
|
||||
*/
|
||||
String ISSUER = "iss";
|
||||
/**
|
||||
* jwt所面向的用户
|
||||
*/
|
||||
String SUBJECT = "sub";
|
||||
/**
|
||||
* 接收jwt的一方
|
||||
*/
|
||||
String AUDIENCE = "aud";
|
||||
/**
|
||||
* jwt的过期时间,这个过期时间必须要大于签发时间
|
||||
*/
|
||||
String EXPIRES_AT = "exp";
|
||||
/**
|
||||
* 生效时间,定义在什么时间之前,该jwt都是不可用的.
|
||||
*/
|
||||
String NOT_BEFORE = "nbf";
|
||||
/**
|
||||
* jwt的签发时间
|
||||
*/
|
||||
String ISSUED_AT = "iat";
|
||||
/**
|
||||
* jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
|
||||
*/
|
||||
String JWT_ID = "jti";
|
||||
|
||||
/**
|
||||
* 设置 jwt签发者("iss")的Payload值
|
||||
*
|
||||
* @param issuer jwt签发者
|
||||
* @return this
|
||||
*/
|
||||
default T setIssuer(String issuer) {
|
||||
return setPayload(ISSUER, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt所面向的用户("sub")的Payload值
|
||||
*
|
||||
* @param subject jwt所面向的用户
|
||||
* @return this
|
||||
*/
|
||||
default T setSubject(String subject) {
|
||||
return setPayload(SUBJECT, subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置接收jwt的一方("aud")的Payload值
|
||||
*
|
||||
* @param audience 接收jwt的一方
|
||||
* @return this
|
||||
*/
|
||||
default T setAudience(String... audience) {
|
||||
return setPayload(AUDIENCE, audience);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt的过期时间("exp")的Payload值,这个过期时间必须要大于签发时间
|
||||
*
|
||||
* @param expiresAt jwt的过期时间
|
||||
* @return this
|
||||
* @see #setIssuedAt(Date)
|
||||
*/
|
||||
default T setExpiresAt(Date expiresAt) {
|
||||
return setPayload(EXPIRES_AT, expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置不可用时间点界限("nbf")的Payload值
|
||||
*
|
||||
* @param notBefore 不可用时间点界限,在这个时间点之前,jwt不可用
|
||||
* @return this
|
||||
*/
|
||||
default T setNotBefore(Date notBefore) {
|
||||
return setPayload(NOT_BEFORE, notBefore);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt的签发时间("iat")
|
||||
*
|
||||
* @param issuedAt 签发时间
|
||||
* @return this
|
||||
*/
|
||||
default T setIssuedAt(Date issuedAt) {
|
||||
return setPayload(ISSUED_AT, issuedAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置jwt的唯一身份标识("jti")
|
||||
*
|
||||
* @param jwtId 唯一身份标识
|
||||
* @return this
|
||||
*/
|
||||
default T setJWTId(String jwtId) {
|
||||
return setPayload(JWT_ID, jwtId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Payload值
|
||||
*
|
||||
* @param name payload名
|
||||
* @param value payload值
|
||||
* @return this
|
||||
*/
|
||||
T setPayload(String name, Object value);
|
||||
}
|
@ -21,7 +21,7 @@ public interface JWTSigner {
|
||||
*
|
||||
* @param headerBase64 JWT头的JSON字符串Base64表示
|
||||
* @param payloadBase64 JWT载荷的JSON字符串Base64表示
|
||||
* @param signBase64 被验证的签名Base64表示
|
||||
* @param signBase64 被验证的签名Base64表示
|
||||
* @return 签名是否一致
|
||||
*/
|
||||
boolean verify(String headerBase64, String payloadBase64, String signBase64);
|
||||
@ -32,4 +32,14 @@ public interface JWTSigner {
|
||||
* @return 算法
|
||||
*/
|
||||
String getAlgorithm();
|
||||
|
||||
/**
|
||||
* 获取算法ID,即算法的简写形式,如HS256
|
||||
*
|
||||
* @return 算法ID
|
||||
* @since 5.7.2
|
||||
*/
|
||||
default String getAlgorithmId() {
|
||||
return AlgorithmUtil.getId(getAlgorithm());
|
||||
}
|
||||
}
|
||||
|
63
hutool-jwt/src/test/java/cn/hutool/jwt/JWTValidatorTest.java
Normal file
63
hutool-jwt/src/test/java/cn/hutool/jwt/JWTValidatorTest.java
Normal file
@ -0,0 +1,63 @@
|
||||
package cn.hutool.jwt;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.exceptions.ValidateException;
|
||||
import cn.hutool.jwt.signers.JWTSignerUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JWTValidatorTest {
|
||||
|
||||
@Test(expected = ValidateException.class)
|
||||
public void expiredAtTest(){
|
||||
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0Nzc1OTJ9.isvT0Pqx0yjnZk53mUFSeYFJLDs-Ls9IsNAm86gIdZo";
|
||||
JWTValidator.of(token).validateDate(DateUtil.date());
|
||||
}
|
||||
|
||||
@Test(expected = ValidateException.class)
|
||||
public void issueAtTest(){
|
||||
final String token = JWT.create()
|
||||
.setIssuedAt(DateUtil.date())
|
||||
.setKey("123456".getBytes())
|
||||
.sign();
|
||||
|
||||
// 签发时间早于被检查的时间
|
||||
JWTValidator.of(token).validateDate(DateUtil.yesterday());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issueAtPassTest(){
|
||||
final String token = JWT.create()
|
||||
.setIssuedAt(DateUtil.date())
|
||||
.setKey("123456".getBytes())
|
||||
.sign();
|
||||
|
||||
// 签发时间早于被检查的时间
|
||||
JWTValidator.of(token).validateDate(DateUtil.date());
|
||||
}
|
||||
|
||||
@Test(expected = ValidateException.class)
|
||||
public void notBeforeTest(){
|
||||
final JWT jwt = JWT.create()
|
||||
.setNotBefore(DateUtil.date());
|
||||
|
||||
JWTValidator.of(jwt).validateDate(DateUtil.yesterday());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notBeforePassTest(){
|
||||
final JWT jwt = JWT.create()
|
||||
.setNotBefore(DateUtil.date());
|
||||
JWTValidator.of(jwt).validateDate(DateUtil.date());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateAlgorithmTest(){
|
||||
final String token = JWT.create()
|
||||
.setNotBefore(DateUtil.date())
|
||||
.setKey("123456".getBytes())
|
||||
.sign();
|
||||
|
||||
// 验证算法
|
||||
JWTValidator.of(token).validateAlgorithm(JWTSignerUtil.hs256("123456".getBytes()));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user