diff --git a/CHANGELOG.md b/CHANGELOG.md index 237545b91..26f56fd72 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.42(2025-10-18) +# 5.8.42(2025-10-22) ### 🐣新特性 * 【core 】 `ListUtil`增加`zip`方法(pr#4052@Github) ### 🐞Bug修复 +* 【jwt 】 修复verify方法在定义alg为`none`时验证失效问题(issue#4105@Github) ------------------------------------------------------------------------------------------------------------- # 5.8.41(2025-10-12) 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 1d7ee5141..23230c17f 100755 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/JWT.java @@ -410,6 +410,16 @@ public class JWT implements RegisteredPayload { signer = NoneJWTSigner.NONE; } + // 用户定义alg为none但是签名器不是NoneJWTSigner + if(NoneJWTSigner.isNone(getAlgorithm()) && !(signer instanceof NoneJWTSigner)){ + throw new JWTException("Alg is 'none' but use: {} !", signer.getClass()); + } + + // alg非none,但签名器是NoneJWTSigner + if(signer instanceof NoneJWTSigner && !NoneJWTSigner.isNone(getAlgorithm())){ + throw new JWTException("Alg is not 'none' but use NoneJWTSigner!"); + } + final List tokens = this.tokens; if (CollUtil.isEmpty(tokens)) { throw new JWTException("No token to verify!"); diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java index 9586680f7..e632c7a89 100755 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java @@ -1,7 +1,7 @@ package cn.hutool.jwt.signers; -import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ReUtil; +import cn.hutool.jwt.JWTException; import java.security.Key; import java.security.KeyPair; @@ -232,10 +232,12 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner createSigner(String algorithmId, byte[] key) { - Assert.notNull(key, "Signer key must be not null!"); - - if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { - return none(); + if (NoneJWTSigner.isNone(algorithmId)) { + if(null == key){ + return none(); + }else{ + throw new JWTException("When key is not null, algorithmId must not be none."); + } } return new HMacJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); } @@ -248,10 +250,12 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner createSigner(String algorithmId, KeyPair keyPair) { - Assert.notNull(keyPair, "Signer key pair must be not null!"); - - if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { - return none(); + if (NoneJWTSigner.isNone(algorithmId)) { + if(null == keyPair){ + return none(); + }else{ + throw new JWTException("When key is not null, algorithmId must not be none."); + } } // issue3205@Github @@ -270,11 +274,14 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner createSigner(String algorithmId, Key key) { - Assert.notNull(key, "Signer key must be not null!"); - - if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { - return NoneJWTSigner.NONE; + if (NoneJWTSigner.isNone(algorithmId)) { + if(null == key){ + return none(); + }else{ + throw new JWTException("When key is not null, algorithmId must not be none."); + } } + if (key instanceof PrivateKey || key instanceof PublicKey) { // issue3205@Github if(ReUtil.isMatch("ES\\d{3}", algorithmId)){ diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java index 39a3e4442..4976ff290 100755 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/NoneJWTSigner.java @@ -10,10 +10,27 @@ import cn.hutool.core.util.StrUtil; */ public class NoneJWTSigner implements JWTSigner { + /** + * 定义一个常量ID_NONE,表示没有ID的情况 + */ public static final String ID_NONE = "none"; + /** + * 创建一个NoneJWTSigner实例,用于处理没有签名的JWT + */ public static NoneJWTSigner NONE = new NoneJWTSigner(); + /** + * 判断给定的算法是否为无签名的算法 + * + * @param alg 算法 + * @return 如果是无签名的算法,则返回true;否则返回false + * @since 5.8.42 + */ + public static boolean isNone(final String alg) { + return StrUtil.isBlank( alg) || StrUtil.equalsIgnoreCase(alg, ID_NONE); + } + @Override public String sign(String headerBase64, String payloadBase64) { return StrUtil.EMPTY; diff --git a/hutool-jwt/src/test/java/cn/hutool/jwt/Issue4105Test.java b/hutool-jwt/src/test/java/cn/hutool/jwt/Issue4105Test.java new file mode 100644 index 000000000..51511af4d --- /dev/null +++ b/hutool-jwt/src/test/java/cn/hutool/jwt/Issue4105Test.java @@ -0,0 +1,67 @@ +package cn.hutool.jwt; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.StrUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +public class Issue4105Test { + + @Test + void verifyNoneTest() { + // {"alg": "none"}.{"exp": 1642196407} + // 当定义alg为none时,校验总是成功 + String head = Base64.encode("{\"alg\": \"none\"}"); + String payload = Base64.encode("{\"exp\": 1642196407}"); + String token = StrUtil.format("{}.{}.", head, payload); + + final JWT jwt = JWTUtil.parseToken(token); + Assertions.assertNull(jwt.getSigner()); + // 对于签名为none的JWT,verify()方法总是返回true + Assertions.assertTrue(jwt.verify()); + + // 对于签名为none的JWT,但是定义了key,不一致报错 + final JWT jwt2 = JWTUtil.parseToken(token); + Assertions.assertThrows(JWTException.class, ()-> jwt2.setKey("123".getBytes(StandardCharsets.UTF_8)).verify()); + } + + @Test + void verifyEmptyTest() { + // {"alg": "none"}.{"exp": 1642196407} + // 当定义alg为none时,校验总是成功 + String head = Base64.encode("{\"alg\": \"\"}"); + String payload = Base64.encode("{\"exp\": 1642196407}"); + String token = StrUtil.format("{}.{}.", head, payload); + + final JWT jwt = JWTUtil.parseToken(token); + Assertions.assertNull(jwt.getSigner()); + // 对于签名为none的JWT,verify()方法总是返回true + Assertions.assertTrue(jwt.verify()); + + // 对于签名为none的JWT,setKey使用的签名总是 + final JWT jwt2 = JWTUtil.parseToken(token); + Assertions.assertThrows(JWTException.class, ()-> jwt2.setKey("123".getBytes(StandardCharsets.UTF_8)).verify()); + } + + @Test + void verifyHs256Test() { + // {"alg": "none"}.{"exp": 1642196407} + // 当定义alg为none时,校验总是成功 + String head = Base64.encode("{\"alg\": \"HS256\"}"); + String payload = Base64.encode("{\"exp\": 1642196407}"); + String token = StrUtil.format("{}.{}.", head, payload); + + final JWT jwt = JWTUtil.parseToken(token); + Assertions.assertNull(jwt.getSigner()); + + // 未定义签名器或key,但是JWT中要求了签名算法,异常 + Assertions.assertThrows(JWTException.class, jwt::verify); + + // 手动定义签名器,但是签名部分为空或不一致,返回false + final JWT jwt2 = JWTUtil.parseToken(token); + Assertions.assertFalse(jwt2.setKey("123".getBytes(StandardCharsets.UTF_8)).verify()); + } + +}