Expected Behavior
Easy way to avoid the call to an OP /.well-known/openid-configuration
in a @SpringBootTest
of an application configured with oauth2Client
(or oauth2Login
).
Context
When working with mocked Authentication
instances (using MockMvc
request post processors, WebTestClient
mutators, or test annotations), no communication with the authorization server should be needed. However, in the case of a @SpringBootTest
for an application configured for OAuth2 with an OP issuer URI, the OpenID configuration is fetched eagerly during application context initialisation.
I found two options, but neither is as convenient as declaring a @MockitoBean
for a Spring Boot @ConditionalOnMissingBean
like we can do for the (Reactive)JwtDecoder
in an oauth2ResourceServer
:
- Testcontainers, but this is super slow and completely overkill to provide no more than OpenID configuration
- Wiremock, lighter and faster than Testcontainers, but still requires additional dependencies, adds some overhead, and requires more configuration than a @MockitoBean
(declare a static WireMockServer
, a @DynamicPropertySource
to bind the issuer URI to this server base URL, and @BeforeAll
/@AfterAll
to start & stop the mocked server)
Comment From: ch4mpy
Workaround using Testcontainers:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.dasniko</groupId>
<artifactId>testcontainers-keycloak</artifactId>
<scope>test</scope>
</dependency>
@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class BffApplicationTests {
@SuppressWarnings("resource")
@Container
private static KeycloakContainer keycloak = new KeycloakContainer().withRealmImportFile("/external-realm.json");
@DynamicPropertySource
static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
registry.add("external-issuer", () -> keycloak.getAuthServerUrl() + "/realms/external");
}
@Test
void contextLoads() {}
}
Comment From: ch4mpy
Workaround using WireMock:
<dependency>
<groupId>org.wiremock.integrations</groupId>
<artifactId>wiremock-spring-boot</artifactId>
<scope>test</scope>
</dependency>
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
@EnableWireMock
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class BffApplicationTests {
private static final String openidConfiguration = """
{
"issuer": "%s/realms/external",
"authorization_endpoint": "%s/realms/external/protocol/openid-connect/auth",
"token_endpoint": "%s/realms/external/protocol/openid-connect/token",
"jwks_uri": "%s/realms/external/protocol/openid-connect/certs",
"subject_types_supported": [
"public",
"pairwise"
]
}
""";
private static final WireMockServer mockOp = new WireMockServer(options().dynamicPort());
@BeforeAll
static void beforeAll() {
mockOp.start();
}
@DynamicPropertySource
static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
final var baseUrl = mockOp.baseUrl();
registry.add("external-issuer", () -> "%s/realms/external".formatted(baseUrl));
mockOp.stubFor(get("/realms/external/.well-known/openid-configuration")
.willReturn(ok().withHeader("Content-Type", "application/json")
.withBody(openidConfiguration.formatted(baseUrl, baseUrl, baseUrl, baseUrl))));
}
@AfterAll
static void afterAll() {
mockOp.stop();
}
@Test
void contextLoads() {}
}