Thursday, May 11, 2017

Spring Security HttpSecurity Configuration Testing

Leave a Comment

I have a Spring Boot + Spring Security application that has severalantMatchers paths; some fullyAuthenticated(), some permitAll().

How to I write a test that verifies SecurityConfiguration has my endpoints under /api/** (and ultimately others) secured correctly?

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {      protected void configure(HttpSecurity http) throws Exception {         http             //...             .antMatchers("/api/**").fullyAuthenticated()     }  } 

Using spring-boot-1.5.2.RELEASE, spring-security-core-4.2.2-release.

Clarification1: I want to as-directly-as-possible test the SecurityConfiguration, as opposed to transitively testing via one of the /api/** endpoints, which may have their own @PreAuthorize security.

Clarification2: I would like something similar to this WebSecurityConfigurerAdapterTests.

3 Answers

Answers 1

I see below test case can help you achieve what you want. It is an Integration Test to test the Web Security configuration and we have similar testing done for all our code that is TDD driven.

@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @WebAppConfiguration public class WebConfigIT {     private MockMvc mockMvc;      @Autowired     private WebApplicationContext webApplicationContext;      @Autowired     private FilterChainProxy springSecurityFilterChain;      @Before     public void setup() throws Exception {         mockMvc = webAppContextSetup(webApplicationContext)                 .addFilter(springSecurityFilterChain)                 .build();     }      @Test     public void testAuthenticationAtAPIURI() throws Exception {         mockMvc.perform(get("/api/xyz"))                 .andExpect(status.is3xxRedirection());     } 

This though looks like doing an explicit testing of the end-point (which is anyways a testing one have to do if doing TDD) but this is also bringing the Spring Security Filter Chain in context to enable you test the Security Context for the APP.

Answers 2

MockMVC should be enough to verify you security configuration since the only thing it mocks is the Http layer. However if you really wish to test your Spring Boot application, Tomcat server and all, you need to use @SpringBootTest, like this

@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) public class NoGoServiceTest {      @LocalServerPort     private int port;      private <T> T makeDepthRequest(NoGoRequest request, NoGoResponse response, String path, Class<T> responseClass) {         testService.addRequestResponseMapping(request, response);          RestTemplate template = new RestTemplate();         HttpHeaders headers = new HttpHeaders();         headers.setContentType(MediaType.APPLICATION_JSON);         headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));         headers.add("Authorization", "Bearer " + tokenProvider.getToken());         RequestEntity<NoGoRequest> requestEntity = new RequestEntity<>(request, headers, HttpMethod.POST, getURI(path));         ResponseEntity<T> responseEntity = template.exchange(requestEntity, responseClass);         return responseEntity.getBody();     }      @SneakyThrows(URISyntaxException.class)     private URI getURI(String path) {         return new URI("http://localhost:" +port + "/nogo" + path);     }      // Test that makes request using `makeDepthRequest` } 

This code is a part on a test taken from an open source project (https://github.com/maritime-web/NoGoService). The basic idea is to start the test on a random port, which Spring will then inject into a field on the test. This allows you to construct URLs and use Springs RestTemplate to make http request to the server, using the same DTO classes as your Controllers. If the authentication mechanism is Basic or Token you simply have to add the correct Authorization header as in this example. If you use Form authentication, then it becomes a bit harder, because you first have to GET /login, then extract the CSRF token and the JSessionId cookie, and the POST them with the credentials to /login, and after login you have to extract the new JSessionId cookie, as the sessionId is changed after login for security reasons.

Hope this was what you needed.

Answers 3

If you want to programatically know which endpoints exist, you can autowire the List of RequestHandlerProvider into your test and filter them based on the path they are exposed on.

@Autowired List<RequestHandlerProvider> handlerProviders;  @Test public void doTest() {     for (RequestHandlerProvider handlerProvider : handlerProviders) {         for (RequestHandler requestHandler : handlerProvider.requestHandlers()) {             for (String pattern : requestHandler.getPatternsCondition().getPatterns()) {                 // call the endpoint without security and check that you get 401             }         }     } }  

Using the RequestHandlerProvider is how SpringFox determines which endpoint are available and their signature, when it build the swagger definition for an API.

Unless you spend a long time building the correct input for each endpoint you will not get 200 OK back from the endpoint when including a valid security token, so you probably have to accept 400 as a correct response.

If you are already worried some developer would make security related mistakes when introducing a new endpoint, I would be equally worried about the logic of the endpoint, which is why I think you should have an integration test for each of them, and that would test your security as well.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment