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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With