From 94306c0d07dceeae837eedc186b8aacad2439833 Mon Sep 17 00:00:00 2001 From: Harrison Date: Tue, 16 Jun 2026 00:35:10 -0700 Subject: [PATCH] KNOX-3355 - Add TrustedOidcIssuerService schema and interface --- .../database/AbstractDataSourceFactory.java | 4 + .../src/main/resources/conf/gateway-site.xml | 49 +++++++ .../createKnoxIDFTrustedOidcIssuersTable.sql | 23 ++++ ...ateKnoxIDFTrustedOidcIssuersTableDerby.sql | 22 ++++ ...teKnoxIDFTrustedOidcIssuersTableOracle.sql | 23 ++++ .../services/AbstractGatewayServicesTest.java | 3 +- .../TrustedOidcIssuerTest.java | 71 ++++++++++ .../TrustedOidcIssuersSchemaTest.java | 121 ++++++++++++++++++ .../knox/gateway/services/ServiceType.java | 3 +- .../trustedoidcissuer/TrustedOidcIssuer.java | 63 +++++++++ .../TrustedOidcIssuerService.java | 96 ++++++++++++++ 11 files changed, 476 insertions(+), 2 deletions(-) create mode 100644 gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTable.sql create mode 100644 gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableDerby.sql create mode 100644 gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableOracle.sql create mode 100644 gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerTest.java create mode 100644 gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuersSchemaTest.java create mode 100644 gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuer.java create mode 100644 gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerService.java diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/database/AbstractDataSourceFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/database/AbstractDataSourceFactory.java index a9d544fe85..a76b219a9e 100644 --- a/gateway-server/src/main/java/org/apache/knox/gateway/database/AbstractDataSourceFactory.java +++ b/gateway-server/src/main/java/org/apache/knox/gateway/database/AbstractDataSourceFactory.java @@ -50,6 +50,10 @@ public abstract class AbstractDataSourceFactory { public static final String DERBY_KNOXIDF_FED_IDENTITY_TABLE_CREATE_SQL_FILE_NAME = "createKnoxIDFFederatedIdentityTableDerby.sql"; public static final String DERBY_KNOXIDF_FED_IDENTITY_ATTR_TABLE_CREATE_SQL_FILE_NAME = "createKnoxIDFFederatedIdentityAttributesTableDerby.sql"; + public static final String KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL = "createKnoxIDFTrustedOidcIssuersTable.sql"; + public static final String DERBY_KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL = "createKnoxIDFTrustedOidcIssuersTableDerby.sql"; + public static final String ORACLE_KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL = "createKnoxIDFTrustedOidcIssuersTableOracle.sql"; + public static final String DATABASE_USER_ALIAS_NAME = "gateway_database_user"; public static final String DATABASE_PASSWORD_ALIAS_NAME = "gateway_database_password"; public static final String DATABASE_TRUSTSTORE_PASSWORD_ALIAS_NAME = "gateway_database_ssl_truststore_password"; diff --git a/gateway-server/src/main/resources/conf/gateway-site.xml b/gateway-server/src/main/resources/conf/gateway-site.xml index 9669a1941c..e29b3ae8d9 100644 --- a/gateway-server/src/main/resources/conf/gateway-site.xml +++ b/gateway-server/src/main/resources/conf/gateway-site.xml @@ -218,4 +218,53 @@ limitations under the License. --> + + + \ No newline at end of file diff --git a/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTable.sql b/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTable.sql new file mode 100644 index 0000000000..a44875524a --- /dev/null +++ b/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTable.sql @@ -0,0 +1,23 @@ +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with this +-- work for additional information regarding copyright ownership. The ASF +-- licenses this file to you 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. + +CREATE TABLE IF NOT EXISTS TRUSTED_OIDC_ISSUERS ( + issuer_url VARCHAR(2048) NOT NULL, + dynamic_jwks BOOLEAN DEFAULT false NOT NULL, + cluster_name VARCHAR(256), + registered_at TIMESTAMP NOT NULL, + registered_by VARCHAR(2048), + PRIMARY KEY (issuer_url) +); diff --git a/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableDerby.sql b/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableDerby.sql new file mode 100644 index 0000000000..973aa6e5f6 --- /dev/null +++ b/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableDerby.sql @@ -0,0 +1,22 @@ +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with this +-- work for additional information regarding copyright ownership. The ASF +-- licenses this file to you 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. + +CREATE TABLE TRUSTED_OIDC_ISSUERS ( + issuer_url VARCHAR(2048) PRIMARY KEY, + dynamic_jwks BOOLEAN DEFAULT false, + cluster_name VARCHAR(256), + registered_at TIMESTAMP, + registered_by VARCHAR(2048) +) diff --git a/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableOracle.sql b/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableOracle.sql new file mode 100644 index 0000000000..d983d0f8e1 --- /dev/null +++ b/gateway-server/src/main/resources/createKnoxIDFTrustedOidcIssuersTableOracle.sql @@ -0,0 +1,23 @@ +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with this +-- work for additional information regarding copyright ownership. The ASF +-- licenses this file to you 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. + +CREATE TABLE TRUSTED_OIDC_ISSUERS ( + issuer_url VARCHAR2(2048) NOT NULL, + dynamic_jwks NUMBER(1) DEFAULT 0 NOT NULL, + cluster_name VARCHAR2(256), + registered_at TIMESTAMP(6) NOT NULL, + registered_by VARCHAR2(2048), + PRIMARY KEY (issuer_url) +) diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java index 1a5483c537..902d62681f 100644 --- a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java +++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java @@ -67,7 +67,8 @@ public void testAddStartAndStop() throws ServiceLifecycleException { ServiceType.REMOTE_CONFIGURATION_MONITOR, ServiceType.GATEWAY_STATUS_SERVICE, ServiceType.LDAP_SERVICE, - ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE + ServiceType.KNOXIDF_FEDERATED_IDENTITY_SERVICE, + ServiceType.TRUSTED_OIDC_ISSUER_SERVICE }; assertNotEquals(ServiceType.values(), orderedServiceTypes); diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerTest.java new file mode 100644 index 0000000000..a728e99ac8 --- /dev/null +++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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 org.apache.knox.gateway.services.knoxidf.trustedoidcissuer; + +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.time.Instant; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class TrustedOidcIssuerTest { + + @Test + public void testGetters() { + Instant now = Instant.now(); + TrustedOidcIssuer issuer = new TrustedOidcIssuer( + "https://issuer.example.com", true, "cluster-a", now, "admin@example.com"); + + assertEquals("https://issuer.example.com", issuer.getIssuerUrl()); + assertTrue(issuer.isDynamicJwks()); + assertEquals("cluster-a", issuer.getClusterName()); + assertEquals(now, issuer.getRegisteredAt()); + assertEquals("admin@example.com", issuer.getRegisteredBy()); + } + + @Test + public void testNullableOptionalFields() { + TrustedOidcIssuer issuer = new TrustedOidcIssuer( + "https://issuer.example.com", false, null, Instant.now(), null); + + assertNull("clusterName should be nullable", issuer.getClusterName()); + assertNull("registeredBy should be nullable", issuer.getRegisteredBy()); + assertFalse(issuer.isDynamicJwks()); + } + + @Test + public void testAllFieldsAreFinal() { + for (Field field : TrustedOidcIssuer.class.getDeclaredFields()) { + assertTrue("Field '" + field.getName() + "' must be final for immutability", + Modifier.isFinal(field.getModifiers())); + } + } + + @Test + public void testNoSetterMethods() { + for (Method method : TrustedOidcIssuer.class.getDeclaredMethods()) { + assertFalse("Setter found in immutable POJO: " + method.getName(), + method.getName().startsWith("set")); + } + } +} diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuersSchemaTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuersSchemaTest.java new file mode 100644 index 0000000000..e8cb56015c --- /dev/null +++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuersSchemaTest.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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 org.apache.knox.gateway.services.knoxidf.trustedoidcissuer; + +import org.apache.commons.io.IOUtils; +import org.apache.knox.gateway.database.AbstractDataSourceFactory; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Validates that the TRUSTED_OIDC_ISSUERS DDL scripts parse and execute + * correctly against in-memory databases. + */ +public class TrustedOidcIssuersSchemaTest { + + private static final String DERBY_DB = "trustedissuers"; + private static final String DERBY_URL = "jdbc:derby:memory:" + DERBY_DB + ";create=true"; + private static final String DERBY_SHUTDOWN_URL = "jdbc:derby:memory:" + DERBY_DB + ";shutdown=true"; + private static final String HSQL_URL = "jdbc:hsqldb:mem:trustedissuersschema;ifexists=false"; + private static final String HSQL_USER = "SA"; + private static final String HSQL_PASSWORD = ""; + + private static Connection derbyConn; + private static Connection hsqlConn; + + @BeforeClass + public static void setUp() throws SQLException { + derbyConn = DriverManager.getConnection(DERBY_URL); + hsqlConn = DriverManager.getConnection(HSQL_URL, HSQL_USER, HSQL_PASSWORD); + } + + @AfterClass + public static void tearDown() throws Exception { + // HSQLDB: follow JDBCTokenStateServiceTest pattern — new connection for SHUTDOWN + try (Connection conn = DriverManager.getConnection(HSQL_URL, HSQL_USER, HSQL_PASSWORD); + Statement stmt = conn.createStatement()) { + stmt.execute("SHUTDOWN"); + } + + // Derby: close the shared connection before issuing shutdown + if (derbyConn != null && !derbyConn.isClosed()) { + derbyConn.close(); + } + try { + DriverManager.getConnection(DERBY_SHUTDOWN_URL); + } catch (SQLException e) { + // Derby signals a successful single-DB shutdown as error code 45000, state "08006" + if (!(e.getErrorCode() == 45000 && "08006".equals(e.getSQLState()))) { + throw e; + } + } + } + + /** + * The Derby-dialect DDL must execute without error in a Derby in-memory + * database and leave the table queryable. + */ + @Test + public void testDerbyDdlCreatesTable() throws Exception { + try (Statement stmt = derbyConn.createStatement()) { + stmt.execute(loadSql(AbstractDataSourceFactory.DERBY_KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL)); + try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM TRUSTED_OIDC_ISSUERS")) { + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + } + } + } + + /** + * The standard SQL script uses IF NOT EXISTS. Running the script twice must + * not throw, confirming idempotency. + */ + @Test + public void testStandardSqlIdempotent() throws Exception { + String sql = loadSql(AbstractDataSourceFactory.KNOXIDF_TRUSTED_OIDC_ISSUERS_TABLE_SQL); + try (Statement stmt = hsqlConn.createStatement()) { + stmt.execute(sql); + // Second execution must succeed due to IF NOT EXISTS + stmt.execute(sql); + try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM TRUSTED_OIDC_ISSUERS")) { + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + } + } + } + + private static String loadSql(String fileName) throws IOException { + try (InputStream is = TrustedOidcIssuersSchemaTest.class.getClassLoader().getResourceAsStream(fileName)) { + assertNotNull("SQL file not found on classpath: " + fileName, is); + return IOUtils.toString(is, StandardCharsets.UTF_8); + } + } +} diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java index 5fbcba3ab5..fc7a4236a9 100644 --- a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java @@ -40,7 +40,8 @@ public enum ServiceType { REMOTE_CONFIGURATION_MONITOR("RemoteConfigurationMonitor"), GATEWAY_STATUS_SERVICE("GatewayStatusService"), LDAP_SERVICE("LDAPService"), - KNOXIDF_FEDERATED_IDENTITY_SERVICE("KnoxIDFFederatedIdentityService"); + KNOXIDF_FEDERATED_IDENTITY_SERVICE("KnoxIDFFederatedIdentityService"), + TRUSTED_OIDC_ISSUER_SERVICE("TrustedOidcIssuerService"); private final String serviceTypeName; private final String shortName; diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuer.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuer.java new file mode 100644 index 0000000000..fd4622209c --- /dev/null +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuer.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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 org.apache.knox.gateway.services.knoxidf.trustedoidcissuer; + +import java.time.Instant; + +public final class TrustedOidcIssuer { + + private final String issuerUrl; + private final boolean dynamicJwks; + private final String clusterName; + private final Instant registeredAt; + private final String registeredBy; + + public TrustedOidcIssuer(String issuerUrl, boolean dynamicJwks, String clusterName, + Instant registeredAt, String registeredBy) { + this.issuerUrl = issuerUrl; + this.dynamicJwks = dynamicJwks; + this.clusterName = clusterName; + this.registeredAt = registeredAt; + this.registeredBy = registeredBy; + } + + public String getIssuerUrl() { + return issuerUrl; + } + + public boolean isDynamicJwks() { + return dynamicJwks; + } + + /** + * @return the cluster name this issuer belongs to, or null if not cluster-scoped + */ + public String getClusterName() { + return clusterName; + } + + public Instant getRegisteredAt() { + return registeredAt; + } + + /** + * @return the identity that registered this issuer, or null if not recorded + */ + public String getRegisteredBy() { + return registeredBy; + } +} diff --git a/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerService.java b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerService.java new file mode 100644 index 0000000000..273dbfa372 --- /dev/null +++ b/gateway-spi/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/TrustedOidcIssuerService.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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 org.apache.knox.gateway.services.knoxidf.trustedoidcissuer; + +import org.apache.knox.gateway.services.Service; + +import java.util.List; +import java.util.Optional; + +/** + * Gateway service managing the registry of OIDC issuers trusted for JWT + * verification in Knox. For issuers registered for dynamic JWKS discovery, + * resolves JWKS URIs via OpenID Connect Discovery 1.0 + * (https://openid.net/specs/openid-connect-discovery-1_0.html) rather than + * requiring statically configured JWKS endpoints. + */ +public interface TrustedOidcIssuerService extends Service { + + /** + * Returns {@code true} if the given issuer URL is currently registered as + * trusted. This is the primary SSRF gate: callers must verify trust before + * requesting any external resource associated with an issuer. + */ + boolean isTrusted(String issuerUrl); + + /** + * Returns {@code true} if the given issuer URL is trusted and configured for + * OIDC discovery-based JWKS resolution. Returns {@code false} if the issuer + * is not trusted, or is trusted but configured for static JWKS only. + *

+ * This method combines the trust check with the discovery-mode check. + * Callers may use it as a single guard without separately calling + * {@link #isTrusted(String)}. + */ + boolean isDynamicJwks(String issuerUrl); + + /** + * Resolves the JWKS URI for the given issuer URL using OIDC discovery. + * Callers should verify that the issuer is trusted and configured for OIDC + * discovery via {@link #isDynamicJwks(String)} before calling this method, + * as that check covers both conditions. + *

+ * Returns {@link Optional#empty()} in all failure cases — including issuer not + * trusted, dynamic JWKS not configured, discovery document unreachable or + * malformed, or any internal error. Failure details are logged internally for + * troubleshooting. Callers should treat an empty result uniformly as + * "no trusted JWKS URI available" without branching on the failure cause. + */ + Optional resolveJwksUri(String issuerUrl); + + /** + * Forces re-resolution of the JWKS URI for the given issuer URL, discarding + * any previously resolved value. Use this when a resolved JWKS URI is suspected + * to be stale (for example, if an issuer has changed its JWKS endpoint). + * Has no effect if the issuer is not registered or does not use OIDC discovery. + */ + void refreshJwksUri(String issuerUrl); + + /** + * Registers a new trusted OIDC issuer. + * + * @throws IllegalStateException if the maximum registered issuer limit is + * reached + * @throws RuntimeException if registration fails due to a storage error such + * as a duplicate issuer URL or a database failure + */ + void register(TrustedOidcIssuer issuer); + + /** + * Removes the given issuer URL from the trusted registry and invalidates any + * previously resolved JWKS URI for that issuer. Returns silently if the issuer + * is not currently registered. + * + * @throws RuntimeException if removal fails due to a storage error + */ + void deregister(String issuerUrl); + + /** + * Returns all currently registered trusted issuers. + */ + List list(); +}