diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a56a690f..989dff586 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
* 【db 】 修复Oracle下别名错误造成的SQL语法啊错误(issue#I3VTQW@Gitee)
* 【core 】 修复ConcurrencyTester重复使用时开始测试未清空之前任务的问题(issue#I3VSDO@Gitee)
* 【poi 】 修复使用BigWriter写出,ExcelWriter修改单元格值失败的问题(issue#I3VSDO@Gitee)
+* 【jwt 】 修复Hmac算法下生成签名是hex的问题
-------------------------------------------------------------------------------------------------------------
diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java
index b4064788a..729fb9ec3 100644
--- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java
+++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java
@@ -199,6 +199,20 @@ public class Base64 {
return Base64Encoder.encodeUrlSafe(FileUtil.readBytes(file));
}
+ /**
+ * 编码为Base64字符串
+ * 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
+ *
+ * @param arr 被编码的数组
+ * @param isMultiLine 在76个char之后是CRLF还是EOF
+ * @param isUrlSafe 是否使用URL安全字符,一般为{@code false}
+ * @return 编码后的bytes
+ * @since 5.7.2
+ */
+ public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
+ return Base64Encoder.encodeStr(arr, isMultiLine, isUrlSafe);
+ }
+
/**
* 编码为Base64
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java
index 457389202..608dbc4bf 100644
--- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java
@@ -126,6 +126,20 @@ public class Base64Encoder {
return StrUtil.str(encodeUrlSafe(source, false), DEFAULT_CHARSET);
}
+ /**
+ * 编码为Base64字符串
+ * 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
+ *
+ * @param arr 被编码的数组
+ * @param isMultiLine 在76个char之后是CRLF还是EOF
+ * @param isUrlSafe 是否使用URL安全字符,在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉,一般为{@code false}
+ * @return 编码后的bytes
+ * @since 5.7.2
+ */
+ public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
+ return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET);
+ }
+
/**
* 编码为Base64
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java
index 30eac9adf..2ca102502 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java
@@ -40,6 +40,16 @@ public class Tree extends LinkedHashMap implements Node {
treeNodeConfig, TreeNodeConfig.DEFAULT_CONFIG);
}
+ /**
+ * 获取节点配置
+ *
+ * @return 节点配置
+ * @since 5.7.2
+ */
+ public TreeNodeConfig getConfig() {
+ return this.treeNodeConfig;
+ }
+
/**
* 获取父节点
*
@@ -172,10 +182,10 @@ public class Tree extends LinkedHashMap implements Node {
* @since 5.6.7
*/
@SafeVarargs
- public final Tree addChildren(Tree... children){
- if(ArrayUtil.isNotEmpty(children)){
+ public final Tree addChildren(Tree... children) {
+ if (ArrayUtil.isNotEmpty(children)) {
List> childrenList = this.getChildren();
- if(null == childrenList){
+ if (null == childrenList) {
childrenList = new ArrayList<>();
setChildren(childrenList);
}
@@ -207,16 +217,17 @@ public class Tree extends LinkedHashMap implements Node {
/**
* 打印
- * @param tree 树
+ *
+ * @param tree 树
* @param writer Writer
* @param intent 缩进量
*/
- private static void printTree(Tree> tree, PrintWriter writer, int intent){
+ private static void printTree(Tree> tree, PrintWriter writer, int intent) {
writer.println(StrUtil.format("{}{}[{}]", StrUtil.repeat(CharUtil.SPACE, intent), tree.getName(), tree.getId()));
writer.flush();
final List extends Tree>> children = tree.getChildren();
- if(CollUtil.isNotEmpty(children)){
+ if (CollUtil.isNotEmpty(children)) {
for (Tree> child : children) {
printTree(child, writer, intent + 2);
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java
index 45ada39ad..85063c779 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java
@@ -60,7 +60,7 @@ public class TreeUtil {
* @param 转换的实体 为数据源里的对象类型
* @param ID类型
* @param list 源数据集合
- * @param rootId 最顶层父id值 一般为 0 之类
+ * @param rootId 最顶层父id值 一般为 0 之类
* @param treeNodeConfig 配置
* @param nodeParser 转换器
* @return List
@@ -77,12 +77,34 @@ public class TreeUtil {
return build(map, rootId);
}
+ /**
+ * 单点树构建,按照权重排序
+ *
+ * @param ID类型
+ * @param map 源数据Map
+ * @param rootId 根节点id值 一般为 0 之类
+ * @return {@link Tree}
+ * @since 5.7.2
+ */
+ public static Tree buildSingle(Map> map, E rootId) {
+ final List> list = build(map, rootId);
+ if (CollUtil.isNotEmpty(list)) {
+ final TreeNodeConfig config = list.get(0).getConfig();
+ final Tree root = new Tree<>(config);
+ root.setId(rootId);
+ root.setChildren(list);
+ return root;
+ }
+
+ return new Tree(null).setId(rootId);
+ }
+
/**
* 树构建,按照权重排序
*
- * @param ID类型
- * @param map 源数据Map
- * @param rootId 最顶层父id值 一般为 0 之类
+ * @param ID类型
+ * @param map 源数据Map
+ * @param rootId 最顶层父id值 一般为 0 之类
* @return List
* @since 5.6.7
*/
@@ -92,13 +114,13 @@ public class TreeUtil {
E parentId;
for (Tree node : eTreeMap.values()) {
parentId = node.getParentId();
- if(ObjectUtil.equals(rootId, parentId)){
+ if (ObjectUtil.equals(rootId, parentId)) {
rootTreeList.add(node);
continue;
}
final Tree parentNode = map.get(parentId);
- if(null != parentNode){
+ if (null != parentNode) {
parentNode.addChildren(node);
}
}
@@ -109,9 +131,9 @@ public class TreeUtil {
* 获取ID对应的节点,如果有多个ID相同的节点,只返回第一个。
* 此方法只查找此节点及子节点,采用递归深度优先遍历。
*
- * @param ID类型
+ * @param ID类型
* @param node 节点
- * @param id ID
+ * @param id ID
* @return 节点
* @since 5.2.4
*/
@@ -121,7 +143,7 @@ public class TreeUtil {
}
final List> children = node.getChildren();
- if(null == children) {
+ if (null == children) {
return null;
}
@@ -145,15 +167,15 @@ public class TreeUtil {
* 比如有个人在研发1部,他上面有研发部,接着上面有技术中心
* 返回结果就是:[研发一部, 研发中心, 技术中心]
*
- * @param 节点ID类型
- * @param node 节点
+ * @param 节点ID类型
+ * @param node 节点
* @param includeCurrentNode 是否包含当前节点的名称
* @return 所有父节点名称列表,node为null返回空List
* @since 5.2.4
*/
public static List getParentsName(Tree node, boolean includeCurrentNode) {
final List result = new ArrayList<>();
- if(null == node){
+ if (null == node) {
return result;
}
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java
index 83da5b44b..eff9c4aeb 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java
@@ -1,5 +1,6 @@
package cn.hutool.crypto.digest;
+import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
@@ -25,8 +26,8 @@ import java.security.MessageDigest;
* 一般的,消息鉴别码用于验证传输于两个共 同享有一个密钥的单位之间的消息。
* HMAC 可以与任何迭代散列函数捆绑使用。MD5 和 SHA-1 就是这种散列函数。HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥。
* 注意:此对象实例化后为非线程安全!
- * @author Looly
*
+ * @author Looly
*/
public class HMac implements Serializable {
private static final long serialVersionUID = 1L;
@@ -34,18 +35,21 @@ public class HMac implements Serializable {
private final MacEngine engine;
// ------------------------------------------------------------------------------------------- Constructor start
+
/**
* 构造,自动生成密钥
+ *
* @param algorithm 算法 {@link HmacAlgorithm}
*/
public HMac(HmacAlgorithm algorithm) {
- this(algorithm, (Key)null);
+ this(algorithm, (Key) null);
}
/**
* 构造
+ *
* @param algorithm 算法 {@link HmacAlgorithm}
- * @param key 密钥
+ * @param key 密钥
*/
public HMac(HmacAlgorithm algorithm, byte[] key) {
this(algorithm.getValue(), key);
@@ -53,8 +57,9 @@ public class HMac implements Serializable {
/**
* 构造
+ *
* @param algorithm 算法 {@link HmacAlgorithm}
- * @param key 密钥
+ * @param key 密钥
*/
public HMac(HmacAlgorithm algorithm, Key key) {
this(algorithm.getValue(), key);
@@ -62,8 +67,9 @@ public class HMac implements Serializable {
/**
* 构造
+ *
* @param algorithm 算法
- * @param key 密钥
+ * @param key 密钥
* @since 4.5.13
*/
public HMac(String algorithm, byte[] key) {
@@ -72,8 +78,9 @@ public class HMac implements Serializable {
/**
* 构造
+ *
* @param algorithm 算法
- * @param key 密钥
+ * @param key 密钥
* @since 4.5.13
*/
public HMac(String algorithm, Key key) {
@@ -82,6 +89,7 @@ public class HMac implements Serializable {
/**
* 构造
+ *
* @param engine MAC算法实现引擎
* @since 4.5.13
*/
@@ -95,15 +103,16 @@ public class HMac implements Serializable {
*
* @return MAC算法引擎
*/
- public MacEngine getEngine(){
+ public MacEngine getEngine() {
return this.engine;
}
// ------------------------------------------------------------------------------------------- Digest
+
/**
* 生成文件摘要
*
- * @param data 被摘要数据
+ * @param data 被摘要数据
* @param charset 编码
* @return 摘要
*/
@@ -122,9 +131,30 @@ public class HMac implements Serializable {
}
/**
- * 生成文件摘要,并转为16进制字符串
+ * 生成文件摘要,并转为Base64
*
* @param data 被摘要数据
+ * @return 摘要
+ */
+ public String digestBase64(String data, boolean isUrlSafe) {
+ return digestBase64(data, CharsetUtil.CHARSET_UTF_8, isUrlSafe);
+ }
+
+ /**
+ * 生成文件摘要,并转为Base64
+ *
+ * @param data 被摘要数据
+ * @param charset 编码
+ * @return 摘要
+ */
+ public String digestBase64(String data, Charset charset, boolean isUrlSafe) {
+ return Base64.encodeStr(digest(data, charset), false, isUrlSafe);
+ }
+
+ /**
+ * 生成文件摘要,并转为16进制字符串
+ *
+ * @param data 被摘要数据
* @param charset 编码
* @return 摘要
*/
@@ -150,12 +180,12 @@ public class HMac implements Serializable {
* @return 摘要bytes
* @throws CryptoException Cause by IOException
*/
- public byte[] digest(File file) throws CryptoException{
+ public byte[] digest(File file) throws CryptoException {
InputStream in = null;
try {
in = FileUtil.getInputStream(file);
return digest(in);
- } finally{
+ } finally {
IoUtil.close(in);
}
}
@@ -215,7 +245,7 @@ public class HMac implements Serializable {
/**
* 生成摘要
*
- * @param data {@link InputStream} 数据流
+ * @param data {@link InputStream} 数据流
* @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes
*/
@@ -227,7 +257,7 @@ public class HMac implements Serializable {
* 生成摘要,并转为16进制字符串
* 使用默认缓存大小,见 {@link IoUtil#DEFAULT_BUFFER_SIZE}
*
- * @param data 被摘要数据
+ * @param data 被摘要数据
* @param bufferLength 缓存长度,不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要
*/
@@ -239,22 +269,23 @@ public class HMac implements Serializable {
* 验证生成的摘要与给定的摘要比较是否一致
* 简单比较每个byte位是否相同
*
- * @param digest 生成的摘要
+ * @param digest 生成的摘要
* @param digestToCompare 需要比较的摘要
* @return 是否一致
- * @since 5.6.8
* @see MessageDigest#isEqual(byte[], byte[])
+ * @since 5.6.8
*/
- public boolean verify(byte[] digest, byte[] digestToCompare){
+ public boolean verify(byte[] digest, byte[] digestToCompare) {
return MessageDigest.isEqual(digest, digestToCompare);
}
/**
* 获取MAC算法块长度
+ *
* @return MAC算法块长度
* @since 5.3.3
*/
- public int getMacLength(){
+ public int getMacLength() {
return this.engine.getMacLength();
}
diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java b/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java
index fe9361557..57f4aa57d 100644
--- a/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java
+++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java
@@ -12,6 +12,8 @@ import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import java.nio.charset.Charset;
+import java.security.Key;
+import java.security.KeyPair;
import java.util.List;
import java.util.Map;
@@ -125,6 +127,30 @@ public class JWT {
return setSigner(JWTSignerUtil.createSigner(algorithmId, key));
}
+ /**
+ * 设置签名算法
+ *
+ * @param algorithmId 签名算法ID,如HS256
+ * @param key 密钥
+ * @return this
+ * @since 5.7.2
+ */
+ public JWT setSigner(String algorithmId, Key key) {
+ return setSigner(JWTSignerUtil.createSigner(algorithmId, key));
+ }
+
+ /**
+ * 设置非对称签名算法
+ *
+ * @param algorithmId 签名算法ID,如HS256
+ * @param keyPair 密钥对
+ * @return this
+ * @since 5.7.2
+ */
+ public JWT setSigner(String algorithmId, KeyPair keyPair) {
+ return setSigner(JWTSignerUtil.createSigner(algorithmId, keyPair));
+ }
+
/**
* 设置签名算法
*
diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java
index 5873c7ddf..daa50b2f1 100644
--- a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java
+++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/HMacJWTSigner.java
@@ -51,7 +51,7 @@ public class HMacJWTSigner implements JWTSigner {
@Override
public String sign(String headerBase64, String payloadBase64) {
- return hMac.digestHex(StrUtil.format("{}.{}", headerBase64, payloadBase64), charset);
+ return hMac.digestBase64(StrUtil.format("{}.{}", headerBase64, payloadBase64), charset, true);
}
@Override
diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java
index 81bfe3f88..722ad4d26 100644
--- a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java
+++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSigner.java
@@ -12,7 +12,7 @@ public interface JWTSigner {
*
* @param headerBase64 JWT头的JSON字符串的Base64表示
* @param payloadBase64 JWT载荷的JSON字符串Base64表示
- * @return 签名结果,即JWT的第三部分
+ * @return 签名结果Base64,即JWT的第三部分
*/
String sign(String headerBase64, String payloadBase64);
diff --git a/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java
index 2de7a81c7..89ae07220 100644
--- a/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java
+++ b/hutool-jwt/src/test/java/cn/hutool/jwt/JWTTest.java
@@ -1,5 +1,6 @@
package cn.hutool.jwt;
+import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.signers.JWTSignerUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -73,4 +74,14 @@ public class JWTTest {
jwt.sign();
}
+
+ @Test
+ public void verifyTest(){
+ String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
+ "eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ." +
+ "aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";
+
+ final boolean verify = JWT.of(token).setKey(StrUtil.utf8Bytes("123456")).verify();
+ Assert.assertTrue(verify);
+ }
}