8

I'm trying to test my spring OAuth2 authorization and authentication in my spring boot application using spring's MockMvc class. The fundamental issue I'm facing is the fact that my custom authentication provider is never called even if I have registered it as one of the authentication providers used by spring security. I followed the spring security tutorial found here and here.

Code snippet: Security configurer class - this is where the custom authentication provider gets added.

@Configuration
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationProvider authenticationProvider;


    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }
}

Custom authentication provider - This should do the actual authentication

@Component
public class UsernamePasswordAuthProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials()
                .toString();

        if ("test".equals(username) && "test".equals(password)) {
            Collection<? extends GrantedAuthority> grantedAuthorityList = authentication
                    .getAuthorities();
            return new UsernamePasswordAuthenticationToken
                    (username, password, grantedAuthorityList);
        } else {
            throw new
                    BadCredentialsException("External system authentication failed");
        }
    }

    @Override
    public boolean supports(Class<?> auth) {
        return true;
    }
}

Spring boot integration test - This is where MockMvc gets instantiated using the web application context

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConfigurationServiceApplication.class)
public class SettingsAPITest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext wac;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;


    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
                .addFilter(springSecurityFilterChain).build();
    }


    @Test 
    public void testGetStatus() throws Exception {
        //execute test
        mockMvc.perform(get("/status")
                .with(httpBasic("test","test")))
                .andDo(print())
                .andExpect(status().isOk());
    }
}

And here is the controller

@RestController
public class StatusController{

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<String> getStatus(){

        return new ResponseEntity<>("I'm Ok", HttpStatus.OK);

    }
}

Running the test returns 401 and putting a break point and debugging through it reveals that the custom authentication provider is never used.

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /status
       Parameters = {}
          Headers = {Authorization=[Basic dGVzdDp0ZXN0]}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = null
          Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate, no-store], Pragma=[no-cache, no-cache], Expires=[0], X-Frame-Options=[DENY], WWW-Authenticate=[Bearer realm="oauth2-resource", error="unauthorized", error_description="Full authentication is required to access this resource"], Content-Type=[application/json;charset=UTF-8]}
     Content type = application/json;charset=UTF-8
             Body = {"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
    Forwarded URL = null    Redirected URL = null
          Cookies = []

java.lang.AssertionError: Status  Expected :200 Actual   :401

I have a feeling that my webapp context configuration is being overriden by spring boot somewhere (as most of the stuff here is autoconfigured by spring boot), but I can't really justify this. Any help is really appreciated!! Thanks

FYI, I have looked at related posts

mmehiretu
  • 81
  • 1
  • 3

3 Answers3

2

I used this tutorial to get authentication provider set up. For testing this is my setup:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class MyControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private RestTemplate restTemplate;


    @TestConfiguration
    static class AdditionalConfig {

        @Bean
        public MyAuthenticationProvider productValidator() {
            return new MyAuthenticationProvider();
        }

    }

    @Test
    public void shouldGetDocuments() throws Exception {
        this.mockMvc.perform(post("/partners/links/")
            .with(httpBasic("user", "password")))
                    .andExpect(status().isOk())
                    .andReturn();
    }
}

Keep in mind that if you forget to provide credentials in your test(in my case basic auth), your custom auth provider won't be called.

gmode
  • 3,601
  • 4
  • 31
  • 39
1

To mock up an AuthenticationProvider use your authorization model in the #RequestBuilder. For example, SecurityMockMvcRequestPostProcessors.httpBasic().

Also if your authorization requires headers you could add them.

@Test
fun `when authenticated user requests a secure endpoint, then success`() {
    val requestBuilder = MockMvcRequestBuilders
            .get(provideFullUrl(SecurityCoreEndpointsTest.Keys.SECURE))
            .with(httpBasic("your-username", "your-password"))
                    .header("Header-Key", "Value")  

    val resultMatcher = MockMvcResultMatchers.status().isOk

    mockMvc.perform(requestBuilder).andExpect(resultMatcher)
}

Note: Using @WithMockUser didn't work for me

GL

Braian Coronel
  • 22,105
  • 4
  • 57
  • 62
0

Can you try changing your mockmvc initialization from

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
            .addFilter(springSecurityFilterChain).build();
}

to

@Before
public void setup() {
    this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
            .apply(springSecurity()).build();
}
Amit
  • 1
  • I have just copied your code and executed after making the changes in SecurityConfig as per my answer. I can see custom authenticator being invoked properly. Can you check if you have changed the security config as per my example? Even after changing it doesn't come to your custom authenticator you can try one thing. You can put a break point in doFilterInternal() of BasicAuthenticationFilter.java from spring-security-web module. In that method your custom authenticator will get invoked from line number 179: Authentication authResult = this.authenticationManager.authenticate(authRequest); – Amit Mar 16 '18 at 00:26
  • One more thing, in your status controller you need to add request mapping for "/status" without that the test fails with 404. – Amit Mar 16 '18 at 00:27
  • @Amit i am having the same problem. I am doing what you suggest in this answer and put a breakpoint in `BasicAuthenticationFilter`, which is not getting hit. In the startup crawl for the test I see security configs, but nothing seems to be firing. any other suggestions? – hvgotcodes Aug 22 '18 at 19:23