From 09b04a77fb1a7f98dc77a89663f40856bcaa835c Mon Sep 17 00:00:00 2001 From: apescale Date: Mon, 31 Aug 2026 11:51:12 +0800 Subject: [PATCH] fix(sso): decode JWT header manually to extract kid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jjwt 0.12 的无密钥 parse() 对已签名 JWT 抛 UnsupportedJwtException, 改为直接 base64url 解码 header 段取 kid。 --- .../casdoor/CasdoorIdTokenVerifier.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/api-gateway/src/main/java/com/deepdata/gateway/infrastructure/casdoor/CasdoorIdTokenVerifier.java b/backend/api-gateway/src/main/java/com/deepdata/gateway/infrastructure/casdoor/CasdoorIdTokenVerifier.java index 68191db..84b42a2 100644 --- a/backend/api-gateway/src/main/java/com/deepdata/gateway/infrastructure/casdoor/CasdoorIdTokenVerifier.java +++ b/backend/api-gateway/src/main/java/com/deepdata/gateway/infrastructure/casdoor/CasdoorIdTokenVerifier.java @@ -1,6 +1,8 @@ package com.deepdata.gateway.infrastructure.casdoor; import com.deepdata.gateway.infrastructure.casdoor.dto.JwksResponse; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; @@ -12,6 +14,7 @@ import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPublicKey; @@ -47,6 +50,8 @@ public class CasdoorIdTokenVerifier { /** JWKS 缓存(kid → RSAPublicKey),证书轮转时靠 TTL / kid 未命中强制刷新 */ private volatile CacheEntry cache; + private static final ObjectMapper MAPPER = new ObjectMapper(); + private record CacheEntry(long fetchedAtMillis, Map keys) {} public CasdoorIdTokenVerifier(CasdoorProperties props) { @@ -127,7 +132,7 @@ public class CasdoorIdTokenVerifier { } private Jws parseWithKid(String idToken, Map keys) { - String kid = (String) Jwts.parser().build().parse(idToken).getHeader().get("kid"); + String kid = headerKid(idToken); RSAPublicKey key = keys.get(kid); if (key == null) { log.warn("casdoor jwks: kid [{}] not cached, size={}", kid, keys.size()); @@ -145,6 +150,28 @@ public class CasdoorIdTokenVerifier { } } + /** + * 直接 base64url 解码 JWT header 段取 {@code kid}。 + * 不能用 {@code Jwts.parser().build().parse(...)}:jjwt 0.12 对已签名 JWT + * 在未提供验签密钥时会抛 {@code UnsupportedJwtException},无法仅取 header。 + */ + private static String headerKid(String idToken) { + int firstDot = idToken.indexOf('.'); + if (firstDot <= 0) { + throw new CasdoorIdTokenException("id_token missing JWS header segment"); + } + try { + String headerJson = new String( + Base64.getUrlDecoder().decode(idToken.substring(0, firstDot)), + StandardCharsets.UTF_8); + return MAPPER.readTree(headerJson).path("kid").asText(null); + } catch (CasdoorIdTokenException e) { + throw e; + } catch (Exception e) { + throw new CasdoorIdTokenException("id_token header unparsable: " + e.getMessage(), e); + } + } + private Mono> loadKeys(boolean forced) { CacheEntry current = cache; long ttlMillis = props.getJwksCacheSeconds() * 1000L;