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() {}
}