修复verify方法在定义alg为none时验证失效问题(issue#4105@Github)

This commit is contained in:
Looly 2025-10-22 00:06:03 +08:00
parent 614b4fd7e5
commit d4790d29e0
6 changed files with 146 additions and 34 deletions

View File

@ -16,6 +16,7 @@
package cn.hutool.v7.crypto.digest.mac; package cn.hutool.v7.crypto.digest.mac;
import cn.hutool.v7.core.lang.Assert;
import cn.hutool.v7.crypto.bc.SmUtil; import cn.hutool.v7.crypto.bc.SmUtil;
import java.security.Key; import java.security.Key;
@ -50,6 +51,7 @@ public class MacEngineFactory {
* @since 5.7.12 * @since 5.7.12
*/ */
public static MacEngine createEngine(final String algorithm, final Key key, final AlgorithmParameterSpec spec) { 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())) { if (algorithm.equalsIgnoreCase(HmacAlgorithm.HmacSM3.getValue())) {
// HmacSM3算法是BC库实现的忽略加盐 // HmacSM3算法是BC库实现的忽略加盐
return SmUtil.createHmacSm3Engine(key.getEncoded()); return SmUtil.createHmacSm3Engine(key.getEncoded());

View File

@ -136,12 +136,7 @@ public class JWT implements RegisteredPayload<JWT> {
* @return this * @return this
*/ */
public JWT setKey(final byte[] key) { public JWT setKey(final byte[] key) {
// 检查头信息中是否有算法信息 return setSigner(getAlgorithm(), key);
final String algorithmId = (String) this.header.getClaim(JWTHeader.ALGORITHM);
if (StrUtil.isNotBlank(algorithmId)) {
return setSigner(algorithmId, key);
}
return setSigner(JWTSignerUtil.hs256(key));
} }
/** /**
@ -420,6 +415,16 @@ public class JWT implements RegisteredPayload<JWT> {
signer = NoneJWTSigner.NONE; 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<String> tokens = this.tokens; final List<String> tokens = this.tokens;
if (CollUtil.isEmpty(tokens)) { if (CollUtil.isEmpty(tokens)) {
throw new JWTException("No token to verify!"); throw new JWTException("No token to verify!");

View File

@ -18,6 +18,7 @@ package cn.hutool.v7.json.jwt.signers;
import cn.hutool.v7.core.lang.Assert; import cn.hutool.v7.core.lang.Assert;
import cn.hutool.v7.core.regex.ReUtil; import cn.hutool.v7.core.regex.ReUtil;
import cn.hutool.v7.json.jwt.JWTException;
import java.security.Key; import java.security.Key;
import java.security.KeyPair; import java.security.KeyPair;
@ -147,7 +148,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner hmd5(final Key key) { public static JWTSigner hmd5(final Key key) {
return createSigner("HMD5",key); return createSigner("HMD5", key);
} }
/** /**
@ -157,7 +158,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner hsha1(final Key key) { public static JWTSigner hsha1(final Key key) {
return createSigner("HSHA1",key); return createSigner("HSHA1", key);
} }
/** /**
@ -167,7 +168,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner sm4cmac(final Key key) { public static JWTSigner sm4cmac(final Key key) {
return createSigner("SM4CMAC",key); return createSigner("SM4CMAC", key);
} }
/** /**
@ -177,7 +178,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner rmd2(final Key key) { public static JWTSigner rmd2(final Key key) {
return createSigner("RMD2",key); return createSigner("RMD2", key);
} }
/** /**
@ -187,7 +188,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner rmd5(final Key key) { public static JWTSigner rmd5(final Key key) {
return createSigner("RMD5",key); return createSigner("RMD5", key);
} }
/** /**
@ -197,7 +198,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner rsha1(final Key key) { public static JWTSigner rsha1(final Key key) {
return createSigner("RSHA1",key); return createSigner("RSHA1", key);
} }
/** /**
@ -207,7 +208,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner dnone(final Key key) { public static JWTSigner dnone(final Key key) {
return createSigner("DNONE",key); return createSigner("DNONE", key);
} }
/** /**
@ -217,7 +218,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner dsha1(final Key key) { public static JWTSigner dsha1(final Key key) {
return createSigner("DSHA1",key); return createSigner("DSHA1", key);
} }
/** /**
@ -227,7 +228,7 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner enone(final Key key) { public static JWTSigner enone(final Key key) {
return createSigner("ENONE",key); return createSigner("ENONE", key);
} }
/** /**
@ -237,22 +238,26 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner esha1(final Key key) { public static JWTSigner esha1(final Key key) {
return createSigner("ESHA1",key); return createSigner("ESHA1", key);
} }
/** /**
* 创建签名器 * 创建签名器<br>
* 当key为{@code null}且签名算法为{@link NoneJWTSigner#NONE}返回无签名的签名器
* *
* @param algorithmId 算法ID{@link AlgorithmUtil} * @param algorithmId 算法ID{@link AlgorithmUtil}{@code null} "" 空格使用无签名签名器
* @param key 密钥 * @param key 密钥
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner createSigner(final String algorithmId, final byte[] key) { public static JWTSigner createSigner(final String algorithmId, final byte[] key) {
Assert.notNull(key, "Signer key must be not null!"); if (NoneJWTSigner.isNone(algorithmId)) {
if(null == key){
if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { return none();
return none(); }else{
throw new JWTException("When key is not null, algorithmId must not be none.");
}
} }
return new HMacJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); return new HMacJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key);
} }
@ -264,15 +269,17 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner createSigner(final String algorithmId, final KeyPair keyPair) { public static JWTSigner createSigner(final String algorithmId, final KeyPair keyPair) {
Assert.notNull(keyPair, "Signer key pair must be not null!"); if (NoneJWTSigner.isNone(algorithmId)) {
if(null == keyPair){
if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { return none();
return none(); }else{
throw new JWTException("When keyPair is not null, algorithmId must not be none.");
}
} }
final String algorithm = AlgorithmUtil.getAlgorithm(algorithmId); final String algorithm = AlgorithmUtil.getAlgorithm(algorithmId);
// issue3205@Github // issue3205@Github
if(ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)){ if (ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)) {
return new EllipticCurveJWTSigner(algorithm, keyPair); return new EllipticCurveJWTSigner(algorithm, keyPair);
} }
@ -287,16 +294,18 @@ public class JWTSignerUtil {
* @return 签名器 * @return 签名器
*/ */
public static JWTSigner createSigner(final String algorithmId, final Key key) { public static JWTSigner createSigner(final String algorithmId, final Key key) {
Assert.notNull(key, "Signer key must be not null!"); if (NoneJWTSigner.isNone(algorithmId)) {
if(null == key){
if (null == algorithmId || NoneJWTSigner.ID_NONE.equals(algorithmId)) { return none();
return NoneJWTSigner.NONE; }else{
throw new JWTException("When key is not null, algorithmId must not be none.");
}
} }
final String algorithm = AlgorithmUtil.getAlgorithm(algorithmId); final String algorithm = AlgorithmUtil.getAlgorithm(algorithmId);
if (key instanceof PrivateKey || key instanceof PublicKey) { if (key instanceof PrivateKey || key instanceof PublicKey) {
// issue3205@Github // issue3205@Github
if(ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)){ if (ReUtil.isMatch(ES_ALGORITHM_PATTERN, algorithmId)) {
return new EllipticCurveJWTSigner(algorithm, key); return new EllipticCurveJWTSigner(algorithm, key);
} }

View File

@ -36,6 +36,16 @@ public class NoneJWTSigner implements JWTSigner {
*/ */
public static NoneJWTSigner NONE = new NoneJWTSigner(); 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 @Override
public String sign(final String headerBase64, final String payloadBase64) { public String sign(final String headerBase64, final String payloadBase64) {

View File

@ -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的JWTverify()方法总是返回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的JWTverify()方法总是返回true
Assertions.assertTrue(jwt.verify());
// 对于签名为none的JWTsetKey使用的签名总是
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());
}
}

View File

@ -19,6 +19,7 @@ package cn.hutool.v7.json.jwt;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.Serial;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@ -29,16 +30,20 @@ public class IssueI5QRUOTest {
// https://jwt.io/ // https://jwt.io/
// 自定义header顺序 // 自定义header顺序
final Map<String, Object> header = new LinkedHashMap<String, Object>(){ final Map<String, Object> header = new LinkedHashMap<>() {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
{ {
put(JWTHeader.ALGORITHM, "HS384"); put(JWTHeader.ALGORITHM, "HS384");
put(JWTHeader.TYPE, "JWT"); put(JWTHeader.TYPE, "JWT");
} }
}; };
final Map<String, Object> payload = new LinkedHashMap<String, Object>(){ final Map<String, Object> payload = new LinkedHashMap<>() {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
{ {
put("sub", "1234567890"); put("sub", "1234567890");
put("name", "John Doe"); put("name", "John Doe");