fix jwt bug

This commit is contained in:
Looly 2021-06-18 15:59:20 +08:00
parent 7ead906910
commit 86d711cc03
10 changed files with 167 additions and 37 deletions

View File

@ -14,6 +14,7 @@
* 【db 】 修复Oracle下别名错误造成的SQL语法啊错误issue#I3VTQW@Gitee * 【db 】 修复Oracle下别名错误造成的SQL语法啊错误issue#I3VTQW@Gitee
* 【core 】 修复ConcurrencyTester重复使用时开始测试未清空之前任务的问题issue#I3VSDO@Gitee * 【core 】 修复ConcurrencyTester重复使用时开始测试未清空之前任务的问题issue#I3VSDO@Gitee
* 【poi 】 修复使用BigWriter写出ExcelWriter修改单元格值失败的问题issue#I3VSDO@Gitee * 【poi 】 修复使用BigWriter写出ExcelWriter修改单元格值失败的问题issue#I3VSDO@Gitee
* 【jwt 】 修复Hmac算法下生成签名是hex的问题
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -199,6 +199,20 @@ public class Base64 {
return Base64Encoder.encodeUrlSafe(FileUtil.readBytes(file)); return Base64Encoder.encodeUrlSafe(FileUtil.readBytes(file));
} }
/**
* 编码为Base64字符串<br>
* 如果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<br> * 编码为Base64<br>
* 如果isMultiLine为{@code true}则每76个字符一个换行符否则在一行显示 * 如果isMultiLine为{@code true}则每76个字符一个换行符否则在一行显示

View File

@ -126,6 +126,20 @@ public class Base64Encoder {
return StrUtil.str(encodeUrlSafe(source, false), DEFAULT_CHARSET); return StrUtil.str(encodeUrlSafe(source, false), DEFAULT_CHARSET);
} }
/**
* 编码为Base64字符串<br>
* 如果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<br> * 编码为Base64<br>
* 如果isMultiLine为{@code true}则每76个字符一个换行符否则在一行显示 * 如果isMultiLine为{@code true}则每76个字符一个换行符否则在一行显示

View File

@ -40,6 +40,16 @@ public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
treeNodeConfig, TreeNodeConfig.DEFAULT_CONFIG); treeNodeConfig, TreeNodeConfig.DEFAULT_CONFIG);
} }
/**
* 获取节点配置
*
* @return 节点配置
* @since 5.7.2
*/
public TreeNodeConfig getConfig() {
return this.treeNodeConfig;
}
/** /**
* 获取父节点 * 获取父节点
* *
@ -172,10 +182,10 @@ public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
* @since 5.6.7 * @since 5.6.7
*/ */
@SafeVarargs @SafeVarargs
public final Tree<T> addChildren(Tree<T>... children){ public final Tree<T> addChildren(Tree<T>... children) {
if(ArrayUtil.isNotEmpty(children)){ if (ArrayUtil.isNotEmpty(children)) {
List<Tree<T>> childrenList = this.getChildren(); List<Tree<T>> childrenList = this.getChildren();
if(null == childrenList){ if (null == childrenList) {
childrenList = new ArrayList<>(); childrenList = new ArrayList<>();
setChildren(childrenList); setChildren(childrenList);
} }
@ -207,16 +217,17 @@ public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
/** /**
* 打印 * 打印
* @param tree *
* @param tree
* @param writer Writer * @param writer Writer
* @param intent 缩进量 * @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.println(StrUtil.format("{}{}[{}]", StrUtil.repeat(CharUtil.SPACE, intent), tree.getName(), tree.getId()));
writer.flush(); writer.flush();
final List<? extends Tree<?>> children = tree.getChildren(); final List<? extends Tree<?>> children = tree.getChildren();
if(CollUtil.isNotEmpty(children)){ if (CollUtil.isNotEmpty(children)) {
for (Tree<?> child : children) { for (Tree<?> child : children) {
printTree(child, writer, intent + 2); printTree(child, writer, intent + 2);
} }

View File

@ -60,7 +60,7 @@ public class TreeUtil {
* @param <T> 转换的实体 为数据源里的对象类型 * @param <T> 转换的实体 为数据源里的对象类型
* @param <E> ID类型 * @param <E> ID类型
* @param list 源数据集合 * @param list 源数据集合
* @param rootId 最顶层父id值 一般为 0 之类 * @param rootId 最顶层父id值 一般为 0 之类
* @param treeNodeConfig 配置 * @param treeNodeConfig 配置
* @param nodeParser 转换器 * @param nodeParser 转换器
* @return List * @return List
@ -77,12 +77,34 @@ public class TreeUtil {
return build(map, rootId); return build(map, rootId);
} }
/**
* 单点树构建按照权重排序
*
* @param <E> ID类型
* @param map 源数据Map
* @param rootId 根节点id值 一般为 0 之类
* @return {@link Tree}
* @since 5.7.2
*/
public static <E> Tree<E> buildSingle(Map<E, Tree<E>> map, E rootId) {
final List<Tree<E>> list = build(map, rootId);
if (CollUtil.isNotEmpty(list)) {
final TreeNodeConfig config = list.get(0).getConfig();
final Tree<E> root = new Tree<>(config);
root.setId(rootId);
root.setChildren(list);
return root;
}
return new Tree<E>(null).setId(rootId);
}
/** /**
* 树构建按照权重排序 * 树构建按照权重排序
* *
* @param <E> ID类型 * @param <E> ID类型
* @param map 源数据Map * @param map 源数据Map
* @param rootId 最顶层父id值 一般为 0 之类 * @param rootId 最顶层父id值 一般为 0 之类
* @return List * @return List
* @since 5.6.7 * @since 5.6.7
*/ */
@ -92,13 +114,13 @@ public class TreeUtil {
E parentId; E parentId;
for (Tree<E> node : eTreeMap.values()) { for (Tree<E> node : eTreeMap.values()) {
parentId = node.getParentId(); parentId = node.getParentId();
if(ObjectUtil.equals(rootId, parentId)){ if (ObjectUtil.equals(rootId, parentId)) {
rootTreeList.add(node); rootTreeList.add(node);
continue; continue;
} }
final Tree<E> parentNode = map.get(parentId); final Tree<E> parentNode = map.get(parentId);
if(null != parentNode){ if (null != parentNode) {
parentNode.addChildren(node); parentNode.addChildren(node);
} }
} }
@ -109,9 +131,9 @@ public class TreeUtil {
* 获取ID对应的节点如果有多个ID相同的节点只返回第一个<br> * 获取ID对应的节点如果有多个ID相同的节点只返回第一个<br>
* 此方法只查找此节点及子节点采用递归深度优先遍历 * 此方法只查找此节点及子节点采用递归深度优先遍历
* *
* @param <T> ID类型 * @param <T> ID类型
* @param node 节点 * @param node 节点
* @param id ID * @param id ID
* @return 节点 * @return 节点
* @since 5.2.4 * @since 5.2.4
*/ */
@ -121,7 +143,7 @@ public class TreeUtil {
} }
final List<Tree<T>> children = node.getChildren(); final List<Tree<T>> children = node.getChildren();
if(null == children) { if (null == children) {
return null; return null;
} }
@ -145,15 +167,15 @@ public class TreeUtil {
* 比如有个人在研发1部他上面有研发部接着上面有技术中心<br> * 比如有个人在研发1部他上面有研发部接着上面有技术中心<br>
* 返回结果就是[研发一部, 研发中心, 技术中心] * 返回结果就是[研发一部, 研发中心, 技术中心]
* *
* @param <T> 节点ID类型 * @param <T> 节点ID类型
* @param node 节点 * @param node 节点
* @param includeCurrentNode 是否包含当前节点的名称 * @param includeCurrentNode 是否包含当前节点的名称
* @return 所有父节点名称列表node为null返回空List * @return 所有父节点名称列表node为null返回空List
* @since 5.2.4 * @since 5.2.4
*/ */
public static <T> List<CharSequence> getParentsName(Tree<T> node, boolean includeCurrentNode) { public static <T> List<CharSequence> getParentsName(Tree<T> node, boolean includeCurrentNode) {
final List<CharSequence> result = new ArrayList<>(); final List<CharSequence> result = new ArrayList<>();
if(null == node){ if (null == node) {
return result; return result;
} }

View File

@ -1,5 +1,6 @@
package cn.hutool.crypto.digest; package cn.hutool.crypto.digest;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
@ -25,8 +26,8 @@ import java.security.MessageDigest;
* 一般的消息鉴别码用于验证传输于两个共 同享有一个密钥的单位之间的消息<br> * 一般的消息鉴别码用于验证传输于两个共 同享有一个密钥的单位之间的消息<br>
* HMAC 可以与任何迭代散列函数捆绑使用MD5 SHA-1 就是这种散列函数HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥<br> * HMAC 可以与任何迭代散列函数捆绑使用MD5 SHA-1 就是这种散列函数HMAC 还可以使用一个用于计算和确认消息鉴别值的密钥<br>
* 注意此对象实例化后为非线程安全 * 注意此对象实例化后为非线程安全
* @author Looly
* *
* @author Looly
*/ */
public class HMac implements Serializable { public class HMac implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -34,18 +35,21 @@ public class HMac implements Serializable {
private final MacEngine engine; private final MacEngine engine;
// ------------------------------------------------------------------------------------------- Constructor start // ------------------------------------------------------------------------------------------- Constructor start
/** /**
* 构造自动生成密钥 * 构造自动生成密钥
*
* @param algorithm 算法 {@link HmacAlgorithm} * @param algorithm 算法 {@link HmacAlgorithm}
*/ */
public HMac(HmacAlgorithm algorithm) { public HMac(HmacAlgorithm algorithm) {
this(algorithm, (Key)null); this(algorithm, (Key) null);
} }
/** /**
* 构造 * 构造
*
* @param algorithm 算法 {@link HmacAlgorithm} * @param algorithm 算法 {@link HmacAlgorithm}
* @param key 密钥 * @param key 密钥
*/ */
public HMac(HmacAlgorithm algorithm, byte[] key) { public HMac(HmacAlgorithm algorithm, byte[] key) {
this(algorithm.getValue(), key); this(algorithm.getValue(), key);
@ -53,8 +57,9 @@ public class HMac implements Serializable {
/** /**
* 构造 * 构造
*
* @param algorithm 算法 {@link HmacAlgorithm} * @param algorithm 算法 {@link HmacAlgorithm}
* @param key 密钥 * @param key 密钥
*/ */
public HMac(HmacAlgorithm algorithm, Key key) { public HMac(HmacAlgorithm algorithm, Key key) {
this(algorithm.getValue(), key); this(algorithm.getValue(), key);
@ -62,8 +67,9 @@ public class HMac implements Serializable {
/** /**
* 构造 * 构造
*
* @param algorithm 算法 * @param algorithm 算法
* @param key 密钥 * @param key 密钥
* @since 4.5.13 * @since 4.5.13
*/ */
public HMac(String algorithm, byte[] key) { public HMac(String algorithm, byte[] key) {
@ -72,8 +78,9 @@ public class HMac implements Serializable {
/** /**
* 构造 * 构造
*
* @param algorithm 算法 * @param algorithm 算法
* @param key 密钥 * @param key 密钥
* @since 4.5.13 * @since 4.5.13
*/ */
public HMac(String algorithm, Key key) { public HMac(String algorithm, Key key) {
@ -82,6 +89,7 @@ public class HMac implements Serializable {
/** /**
* 构造 * 构造
*
* @param engine MAC算法实现引擎 * @param engine MAC算法实现引擎
* @since 4.5.13 * @since 4.5.13
*/ */
@ -95,15 +103,16 @@ public class HMac implements Serializable {
* *
* @return MAC算法引擎 * @return MAC算法引擎
*/ */
public MacEngine getEngine(){ public MacEngine getEngine() {
return this.engine; return this.engine;
} }
// ------------------------------------------------------------------------------------------- Digest // ------------------------------------------------------------------------------------------- Digest
/** /**
* 生成文件摘要 * 生成文件摘要
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param charset 编码 * @param charset 编码
* @return 摘要 * @return 摘要
*/ */
@ -122,9 +131,30 @@ public class HMac implements Serializable {
} }
/** /**
* 生成文件摘要并转为16进制字符串 * 生成文件摘要并转为Base64
* *
* @param data 被摘要数据 * @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 编码 * @param charset 编码
* @return 摘要 * @return 摘要
*/ */
@ -150,12 +180,12 @@ public class HMac implements Serializable {
* @return 摘要bytes * @return 摘要bytes
* @throws CryptoException Cause by IOException * @throws CryptoException Cause by IOException
*/ */
public byte[] digest(File file) throws CryptoException{ public byte[] digest(File file) throws CryptoException {
InputStream in = null; InputStream in = null;
try { try {
in = FileUtil.getInputStream(file); in = FileUtil.getInputStream(file);
return digest(in); return digest(in);
} finally{ } finally {
IoUtil.close(in); 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} 做为默认值 * @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要bytes * @return 摘要bytes
*/ */
@ -227,7 +257,7 @@ public class HMac implements Serializable {
* 生成摘要并转为16进制字符串<br> * 生成摘要并转为16进制字符串<br>
* 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE} * 使用默认缓存大小 {@link IoUtil#DEFAULT_BUFFER_SIZE}
* *
* @param data 被摘要数据 * @param data 被摘要数据
* @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值 * @param bufferLength 缓存长度不足1使用 {@link IoUtil#DEFAULT_BUFFER_SIZE} 做为默认值
* @return 摘要 * @return 摘要
*/ */
@ -239,22 +269,23 @@ public class HMac implements Serializable {
* 验证生成的摘要与给定的摘要比较是否一致<br> * 验证生成的摘要与给定的摘要比较是否一致<br>
* 简单比较每个byte位是否相同 * 简单比较每个byte位是否相同
* *
* @param digest 生成的摘要 * @param digest 生成的摘要
* @param digestToCompare 需要比较的摘要 * @param digestToCompare 需要比较的摘要
* @return 是否一致 * @return 是否一致
* @since 5.6.8
* @see MessageDigest#isEqual(byte[], byte[]) * @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); return MessageDigest.isEqual(digest, digestToCompare);
} }
/** /**
* 获取MAC算法块长度 * 获取MAC算法块长度
*
* @return MAC算法块长度 * @return MAC算法块长度
* @since 5.3.3 * @since 5.3.3
*/ */
public int getMacLength(){ public int getMacLength() {
return this.engine.getMacLength(); return this.engine.getMacLength();
} }

View File

@ -12,6 +12,8 @@ import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil; import cn.hutool.jwt.signers.JWTSignerUtil;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.Key;
import java.security.KeyPair;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -125,6 +127,30 @@ public class JWT {
return setSigner(JWTSignerUtil.createSigner(algorithmId, key)); 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));
}
/** /**
* 设置签名算法 * 设置签名算法
* *

View File

@ -51,7 +51,7 @@ public class HMacJWTSigner implements JWTSigner {
@Override @Override
public String sign(String headerBase64, String payloadBase64) { 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 @Override

View File

@ -12,7 +12,7 @@ public interface JWTSigner {
* *
* @param headerBase64 JWT头的JSON字符串的Base64表示 * @param headerBase64 JWT头的JSON字符串的Base64表示
* @param payloadBase64 JWT载荷的JSON字符串Base64表示 * @param payloadBase64 JWT载荷的JSON字符串Base64表示
* @return 签名结果即JWT的第三部分 * @return 签名结果Base64即JWT的第三部分
*/ */
String sign(String headerBase64, String payloadBase64); String sign(String headerBase64, String payloadBase64);

View File

@ -1,5 +1,6 @@
package cn.hutool.jwt; package cn.hutool.jwt;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.signers.JWTSignerUtil; import cn.hutool.jwt.signers.JWTSignerUtil;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -73,4 +74,14 @@ public class JWTTest {
jwt.sign(); 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);
}
} }