fix(sso): decode JWT header manually to extract kid
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (java-kotlin) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
CodeQL Advanced / Analyze (python) (push) Canceled after 0s
Docker Image Build & Push / manifest (push) Canceled after 0s
Docker Image Build & Push / build-and-push-arm (push) Canceled after 0s
Docker Image Build & Push / build-and-push-amd (push) Canceled after 0s
Gateway Docker Image CI / Build and Push Gateway Docker Image (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (java-kotlin) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
CodeQL Advanced / Analyze (python) (push) Canceled after 0s
Docker Image Build & Push / manifest (push) Canceled after 0s
Docker Image Build & Push / build-and-push-arm (push) Canceled after 0s
Docker Image Build & Push / build-and-push-amd (push) Canceled after 0s
Gateway Docker Image CI / Build and Push Gateway Docker Image (push) Canceled after 0s
jjwt 0.12 的无密钥 parse() 对已签名 JWT 抛 UnsupportedJwtException, 改为直接 base64url 解码 header 段取 kid。
This commit is contained in:
+28
-1
@@ -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<String, RSAPublicKey> keys) {}
|
||||
|
||||
public CasdoorIdTokenVerifier(CasdoorProperties props) {
|
||||
@@ -127,7 +132,7 @@ public class CasdoorIdTokenVerifier {
|
||||
}
|
||||
|
||||
private Jws<Claims> parseWithKid(String idToken, Map<String, RSAPublicKey> 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<Map<String, RSAPublicKey>> loadKeys(boolean forced) {
|
||||
CacheEntry current = cache;
|
||||
long ttlMillis = props.getJwksCacheSeconds() * 1000L;
|
||||
|
||||
Reference in New Issue
Block a user