diff --git a/hutool-crypto/src/main/java/cn/hutool/v7/crypto/digest/mac/MacEngineFactory.java b/hutool-crypto/src/main/java/cn/hutool/v7/crypto/digest/mac/MacEngineFactory.java index 764f0383d..5258ceabe 100644 --- a/hutool-crypto/src/main/java/cn/hutool/v7/crypto/digest/mac/MacEngineFactory.java +++ b/hutool-crypto/src/main/java/cn/hutool/v7/crypto/digest/mac/MacEngineFactory.java @@ -16,6 +16,7 @@ package cn.hutool.v7.crypto.digest.mac; +import cn.hutool.v7.core.lang.Assert; import cn.hutool.v7.crypto.bc.SmUtil; import java.security.Key; @@ -50,6 +51,7 @@ public class MacEngineFactory { * @since 5.7.12 */ public static MacEngine createEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) { + Assert.notBlank(algorithm, "Algorithm must be not blank!"); if (algorithm.equalsIgnoreCase(HmacAlgorithm.HmacSM3.getValue())) { // HmacSM3算法是BC库实现的,忽略加盐 return SmUtil.createHmacSm3Engine(key.getEncoded()); diff --git a/hutool-json/src/main/java/cn/hutool/v7/json/jwt/JWT.java b/hutool-json/src/main/java/cn/hutool/v7/json/jwt/JWT.java index 8033e024e..da31ecb6e 100644 --- a/hutool-json/src/main/java/cn/hutool/v7/json/jwt/JWT.java +++ b/hutool-json/src/main/java/cn/hutool/v7/json/jwt/JWT.java @@ -136,12 +136,7 @@ public class JWT implements RegisteredPayload { * @return this */ public JWT setKey(final byte[] key) { - // 检查头信息中是否有算法信息 - final String algorithmId = (String) this.header.getClaim(JWTHeader.ALGORITHM); - if (StrUtil.isNotBlank(algorithmId)) { - return setSigner(algorithmId, key); - } - return setSigner(JWTSignerUtil.hs256(key)); + return setSigner(getAlgorithm(), key); } /** @@ -420,6 +415,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-json/src/main/java/cn/hutool/v7/json/jwt/signers/JWTSignerUtil.java b/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/JWTSignerUtil.java index 311815875..5f0094579 100644 --- a/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/JWTSignerUtil.java +++ b/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/JWTSignerUtil.java @@ -18,6 +18,7 @@ package cn.hutool.v7.json.jwt.signers; import cn.hutool.v7.core.lang.Assert; import cn.hutool.v7.core.regex.ReUtil; +import cn.hutool.v7.json.jwt.JWTException; import java.security.Key; import java.security.KeyPair; @@ -147,7 +148,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner hmd5(final Key key) { - return createSigner("HMD5",key); + return createSigner("HMD5", key); } /** @@ -157,7 +158,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner hsha1(final Key key) { - return createSigner("HSHA1",key); + return createSigner("HSHA1", key); } /** @@ -167,7 +168,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner sm4cmac(final Key key) { - return createSigner("SM4CMAC",key); + return createSigner("SM4CMAC", key); } /** @@ -177,7 +178,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner rmd2(final Key key) { - return createSigner("RMD2",key); + return createSigner("RMD2", key); } /** @@ -187,7 +188,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner rmd5(final Key key) { - return createSigner("RMD5",key); + return createSigner("RMD5", key); } /** @@ -197,7 +198,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner rsha1(final Key key) { - return createSigner("RSHA1",key); + return createSigner("RSHA1", key); } /** @@ -207,7 +208,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner dnone(final Key key) { - return createSigner("DNONE",key); + return createSigner("DNONE", key); } /** @@ -217,7 +218,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner dsha1(final Key key) { - return createSigner("DSHA1",key); + return createSigner("DSHA1", key); } /** @@ -227,7 +228,7 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner enone(final Key key) { - return createSigner("ENONE",key); + return createSigner("ENONE", key); } /** @@ -237,22 +238,26 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner esha1(final Key key) { - return createSigner("ESHA1",key); + return createSigner("ESHA1", key); } /** - * 创建签名器 + * 创建签名器
+ * 当key为{@code null}且签名算法为{@link NoneJWTSigner#NONE}时,返回无签名的签名器 * - * @param algorithmId 算法ID,见{@link AlgorithmUtil} + * @param algorithmId 算法ID,见{@link AlgorithmUtil},{@code null} "" 空格使用无签名签名器 * @param key 密钥 * @return 签名器 */ public static JWTSigner createSigner(final String algorithmId, final 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); } @@ -264,15 +269,17 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner createSigner(final String algorithmId, final 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 keyPair is not null, algorithmId must not be none."); + } } final String algorithm = AlgorithmUtil.getAlgorithm(algorithmId); // issue3205@Github - if(ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)){ + if (ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)) { return new EllipticCurveJWTSigner(algorithm, keyPair); } @@ -287,16 +294,18 @@ public class JWTSignerUtil { * @return 签名器 */ public static JWTSigner createSigner(final String algorithmId, final 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."); + } } final String algorithm = AlgorithmUtil.getAlgorithm(algorithmId); if (key instanceof PrivateKey || key instanceof PublicKey) { // issue3205@Github - if(ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)){ + if (ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)) { return new EllipticCurveJWTSigner(algorithm, key); } diff --git a/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/NoneJWTSigner.java b/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/NoneJWTSigner.java index 9e95c45c2..6f40e87b4 100644 --- a/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/NoneJWTSigner.java +++ b/hutool-json/src/main/java/cn/hutool/v7/json/jwt/signers/NoneJWTSigner.java @@ -36,6 +36,16 @@ public class NoneJWTSigner implements JWTSigner { */ public static NoneJWTSigner NONE = new NoneJWTSigner(); + /** + * 判断给定的算法是否为无签名的算法 + * + * @param alg 算法 + * @return 如果是无签名的算法,则返回true;否则返回false + */ + public static boolean isNone(final String alg) { + return StrUtil.isBlank( alg) || StrUtil.equalsIgnoreCase(alg, ID_NONE); + } + @Override public String sign(final String headerBase64, final String payloadBase64) { diff --git a/hutool-json/src/test/java/cn/hutool/v7/json/jwt/Issue4105Test.java b/hutool-json/src/test/java/cn/hutool/v7/json/jwt/Issue4105Test.java new file mode 100644 index 000000000..dd83b6c69 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/v7/json/jwt/Issue4105Test.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.hutool.v7.json.jwt; + +import cn.hutool.v7.core.codec.binary.Base64; +import cn.hutool.v7.core.text.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()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/v7/json/jwt/IssueI5QRUOTest.java b/hutool-json/src/test/java/cn/hutool/v7/json/jwt/IssueI5QRUOTest.java index 4029beb54..83280da32 100644 --- a/hutool-json/src/test/java/cn/hutool/v7/json/jwt/IssueI5QRUOTest.java +++ b/hutool-json/src/test/java/cn/hutool/v7/json/jwt/IssueI5QRUOTest.java @@ -19,6 +19,7 @@ package cn.hutool.v7.json.jwt; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.io.Serial; import java.util.LinkedHashMap; import java.util.Map; @@ -29,16 +30,20 @@ public class IssueI5QRUOTest { // https://jwt.io/ // 自定义header顺序 - final Map header = new LinkedHashMap(){ + final Map header = new LinkedHashMap<>() { + @Serial private static final long serialVersionUID = 1L; + { put(JWTHeader.ALGORITHM, "HS384"); put(JWTHeader.TYPE, "JWT"); } }; - final Map payload = new LinkedHashMap(){ + final Map payload = new LinkedHashMap<>() { + @Serial private static final long serialVersionUID = 1L; + { put("sub", "1234567890"); put("name", "John Doe");