spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.83k stars 5.9k forks source link

WebMvcTest seems to rely on a running OAuth2 provider #11784

Closed straurob closed 2 years ago

straurob commented 2 years ago

Describe the bug My Spring Boot application uses spring-boot-starter-oauth2-client and spring-boot-starter-oauth2-resource-server. The application provides a simple REST controller returning the OAuth2 user's information. I created a @WebMvcTest to verify the controller's behavior.

However, this test attempts to create a real connection to the identiy provider's URL which is configured by spring.security.oauth2.client.provider.keycloak.issuer-uri.

There is a similar issue #7624 but this is about @SpringBootTest and not @WebMvcTest.

Caused by: java.lang.IllegalArgumentException: Unable to resolve Configuration with the provided Issuer of "http://localhost:8080/auth/realms/myrealm"
    at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:220)
    at org.springframework.security.oauth2.client.registration.ClientRegistrations.fromIssuerLocation(ClientRegistrations.java:144)
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getBuilderFromIssuerIfPossible(OAuth2ClientPropertiesRegistrationAdapter.java:83)
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getClientRegistration(OAuth2ClientPropertiesRegistrationAdapter.java:59)
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.lambda$getClientRegistrations$0(OAuth2ClientPropertiesRegistrationAdapter.java:53)
    at java.util.HashMap.forEach(HashMap.java:1290)
    at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(OAuth2ClientPropertiesRegistrationAdapter.java:52)
    at org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration.clientRegistrationRepository(OAuth2ClientRegistrationRepositoryConfiguration.java:49)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
    ... 125 more
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/auth/realms/myrealm/.well-known/openid-configuration": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:785)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:670)
    at org.springframework.security.oauth2.client.registration.ClientRegistrations.lambda$oidc$0(ClientRegistrations.java:155)
    at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:208)
    ... 137 more
Caused by: java.net.ConnectException: Connection refused (Connection refused)
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:607)
    at java.net.Socket.connect(Socket.java:556)
    at sun.net.NetworkClient.doConnect(NetworkClient.java:180)
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:463)
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:558)
    at sun.net.www.http.HttpClient.<init>(HttpClient.java:242)
    at sun.net.www.http.HttpClient.New(HttpClient.java:339)
    at sun.net.www.http.HttpClient.New(HttpClient.java:357)
    at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1228)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1162)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1056)
    at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:990)
    at org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:76)
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:776)
    ... 140 more

Expected behavior The @WebMvcTest should not rely on a running instance of an identity provider.

Sample

The complete example is also available on GitHub: https://github.com/straurob/spring-security-11784

@RestController
public class UserController {

    @GetMapping("/api/user")
    public String getUser(@AuthenticationPrincipal OAuth2User user) {
        return user.getName();
    }
}
@WebMvcTest(controllers = UserController.class)
class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    void shouldReturnUsername() throws Exception {
        mockMvc.perform(get("/api/user").with(oauth2Login())).andExpect(status().isOk());
    }
}
@Configuration
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors().and()
                .csrf().disable()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .oauth2Login();
        return httpSecurity.build();
    }
}
spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: test-client-id
            client-secret: test-client-secret
            authorization-grant-type: authorization_code
            scope: openid
        provider:
          keycloak:
            issuer-uri: http://localhost:8080/auth/realms/myrealm
            user-name-attribute: preferred_username
jgrandja commented 2 years ago

@straurob The javadoc for @WebMvcTest states the following:

By default, tests annotated with @WebMvcTest will also auto-configure Spring Security ...

I've tried your sample and confirmed that OAuth2ClientRegistrationRepositoryConfiguration is indeed bootstrapped, which explains the error you are getting. You will need to make some changes to your test.

I'm going to close this as the behaviour is expected based on the test configuration.

straurob commented 2 years ago

In case anyone else will stumble upon this, it seems as if the exclusion of OAuth2ClientAutoConfiguration resolves the issue:

@WebMvcTest(excludeAutoConfiguration = OAuth2ClientAutoConfiguration.class)

acaligiuri commented 1 year ago

@straurob that doesn't work if you're trying to test your security. It would seem you can't mock your authentication server in a spring-security test. Am I missing something here?

daliborfilus commented 3 months ago

I'm also fighting with this.

I have to remove all spring.security.oauth2.client.* from application.yml, so it's not picked up by the test. (And create another profile for the actual dev and production app.)

But then I get Parameter 0 of method setFilterChains in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found. when the test starts, it won't boot.

That means I have to disable my SecurityConfiguration class, i.e. the one with @EnableWebSecurity annotation.

If I do that, then I can't use mvc.with(oidcLogin()). It does nothing. I.e. in the controller's parameter @AuthenticationPrincipal principal: OidcUser, the value is always null.

I want to test if the security rules and filters are correct and ODIC roles are correctly mapped to them (in a test marked with @SpringBootTest using MockMvc). I can't find any real example of such configuration.

The closest I found is https://github.com/ch4mpy/spring-addons/blob/master/samples/tutorials/servlet-client/src/test/java/com/c4soft/springaddons/tutorials/ServletClientApplicationTests.java which actually uses @SpringBootTest with MockMvc and has everything in application.yml filled-in, so the autoconfiguration works.

But that repository mocks InMemoryClientRegistrationRepository to get it to work. Is that the recommended way?