feat: add CAMELEER_OIDC_JWK_SET_URI for direct JWKS fetching
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / build (push) Has been cancelled

When set, fetches JWKs from this URL directly instead of discovering
from the OIDC well-known endpoint. Needed when the public issuer URL
(e.g., https://domain.com/oidc) isn't reachable from inside containers
but the internal URL (http://logto:3001/oidc/jwks) is.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-05 21:02:51 +02:00
parent 12bb734c2d
commit 3c70313d78
3 changed files with 23 additions and 10 deletions

View File

@@ -155,17 +155,26 @@ public class SecurityConfig {
private JwtDecoder buildOidcDecoder(SecurityProperties properties) throws Exception {
String issuerUri = properties.getOidcIssuerUri();
// Discover JWKS URI and supported algorithms from OIDC discovery
String discoveryUrl = issuerUri.endsWith("/")
? issuerUri + ".well-known/openid-configuration"
: issuerUri + "/.well-known/openid-configuration";
URL url = new URI(discoveryUrl).toURL();
OIDCProviderMetadata metadata;
try (InputStream in = url.openStream()) {
JSONObject json = (JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(in);
metadata = OIDCProviderMetadata.parse(json);
// Resolve JWKS URI: use explicit config if set, otherwise discover from OIDC metadata.
// Explicit URI is needed when the public issuer URL isn't reachable from inside
// containers (e.g., https://domain.com/oidc) but the internal URL is (http://logto:3001/oidc/jwks).
URL jwksUri;
String jwkSetUri = properties.getOidcJwkSetUri();
if (jwkSetUri != null && !jwkSetUri.isBlank()) {
jwksUri = new URI(jwkSetUri).toURL();
log.info("Using explicit JWKS URI: {}", jwksUri);
} else {
String discoveryUrl = issuerUri.endsWith("/")
? issuerUri + ".well-known/openid-configuration"
: issuerUri + "/.well-known/openid-configuration";
URL url = new URI(discoveryUrl).toURL();
OIDCProviderMetadata metadata;
try (InputStream in = url.openStream()) {
JSONObject json = (JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(in);
metadata = OIDCProviderMetadata.parse(json);
}
jwksUri = metadata.getJWKSetURI().toURL();
}
URL jwksUri = metadata.getJWKSetURI().toURL();
// Build decoder supporting ES384 (Logto default) and ES256, RS256
var jwkSource = JWKSourceBuilder.create(jwksUri).build();

View File

@@ -18,6 +18,7 @@ public class SecurityProperties {
private String uiOrigin;
private String jwtSecret;
private String oidcIssuerUri;
private String oidcJwkSetUri;
private String oidcAudience;
public long getAccessTokenExpiryMs() { return accessTokenExpiryMs; }
@@ -38,6 +39,8 @@ public class SecurityProperties {
public void setJwtSecret(String jwtSecret) { this.jwtSecret = jwtSecret; }
public String getOidcIssuerUri() { return oidcIssuerUri; }
public void setOidcIssuerUri(String oidcIssuerUri) { this.oidcIssuerUri = oidcIssuerUri; }
public String getOidcJwkSetUri() { return oidcJwkSetUri; }
public void setOidcJwkSetUri(String oidcJwkSetUri) { this.oidcJwkSetUri = oidcJwkSetUri; }
public String getOidcAudience() { return oidcAudience; }
public void setOidcAudience(String oidcAudience) { this.oidcAudience = oidcAudience; }
}

View File

@@ -51,6 +51,7 @@ security:
ui-origin: ${CAMELEER_UI_ORIGIN:http://localhost:5173}
jwt-secret: ${CAMELEER_JWT_SECRET:}
oidc-issuer-uri: ${CAMELEER_OIDC_ISSUER_URI:}
oidc-jwk-set-uri: ${CAMELEER_OIDC_JWK_SET_URI:}
oidc-audience: ${CAMELEER_OIDC_AUDIENCE:}