2

I looking for a way how to test Spring Boot REST API with following setup:

@RestController
class SomeRestController {

  @Autowired
  private SomeService someService;

  @GetMapping("/getSome")
  @PreAuthorize("@canGetSome.isValid()")
  public SomeObject getSomeObject() {
    return someService.getSomeObject();
  }
}

_

@Component
public class CanGetSome{

  @Autowired
  private final LoggedUser loggedUser;

  public boolean isValid() {
    return loggedUser.getPermissions().isCanGetSome();
  }
}

_

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
...
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

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

//custom LoggedUser object which is initzialized on authentication sucessfull
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public LoggedUser loggedUser() {
  Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  return (LoggedUser) authentication.getPrincipal();
  }
...
}

My test case:

@SpringBootTest(
    classes = SpringBootApplication,
    webEnvironment = RANDOM_PORT)
@ContextConfiguration
class RestSecurityTest extends Specification {

  @Autowired
  private TestRestTemplate testRestTemplate

  @LocalServerPort
  private int port

  @WithCustomMockUser
  def "test testRestTemplare"(){
  expect:
  def respone = testRestTemplate.getForObject('http://localhost:'+ port+'/getSome', String)
  }

_

public class WithCustomMockUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> {

  @Override
  public SecurityContext createSecurityContext(WithCustomMockUser annotation) {
    //init securityContext like @WithMockUser but with LoggedUser as principal which return true on loggedUser.getPermissions().isCanGetSome();
  }
}

Unfortunately I getting following response in test:

{
  "timestamp": 1524759173851,
  "status": 403,
  "error": "Forbidden",
  "message": "Access Denied",
  "path": "/getSome"
}

I also debug different spring filters after request and there SecurityContext authentication is null and later is switched to AnonymousAuthenticationToken

I don't know why SecurityContext is a null after request and isn't SecurityContext which is initialized with @WithCustomMockUser annotation. Any ideas how to fix it ?

Kamil W
  • 2,230
  • 2
  • 21
  • 43
  • Does it work *outside* of your tests? – Makoto Apr 26 '18 at 16:38
  • 1
    Did you try [this](https://stackoverflow.com/a/40921028/2387977)? I passed for a similar problem and this solution (with some changes) works for me. Try it and tell us the results here. PS.: try to use MockMvc, I couldn't make it work with TestRestTemplate. – Dherik Apr 26 '18 at 16:52

1 Answers1

2

Spring MVC should be the way to go.

Test class:-

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestContext.class, TestWebMvcConfigurerAdapter.class, SecurityConfiguration .class })
@WebAppConfiguration
public class SomeControllerTest {

    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    @Before
    public void setUp() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
                .addFilters(this.springSecurityFilterChain)
                .build();
    }

    @Test
    public void shouldPreAuthorise() throws Exception {
        this.mockMvc.perform(get("/getSome")
                .with(user("user.name"))
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }

}

Test web configurer:-

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
        "your.package.here"
})
public class TestWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {

}

Some mock objects:-

@Configuration
public class TestContext {

    @Bean
    public SomeService someService() {
        return mock(SomeService.class);
    }

}
skypootle
  • 21
  • 3