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> 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); + } }