diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java index b388d793..e7349042 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityConfig.java @@ -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(); diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java index 59509c32..100525cd 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java @@ -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; } } diff --git a/cameleer3-server-app/src/main/resources/application.yml b/cameleer3-server-app/src/main/resources/application.yml index 75f03a78..3dfe2cc0 100644 --- a/cameleer3-server-app/src/main/resources/application.yml +++ b/cameleer3-server-app/src/main/resources/application.yml @@ -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:}