I'm trying to test #WebMvcTest with custom security settings defined in SecurityConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin*").access("hasRole('ADMIN')").antMatchers("/**").permitAll().and().formLogin();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("ADMIN");
}
}
Test class is:
#RunWith(SpringRunner.class)
#WebMvcTest(value = ExampleController.class)
public class ExampleControllerMockMVCTest {
#Autowired
private MockMvc mockMvc;
#Test
public void indexTest() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"));
}
#Test
public void adminTestWithoutAuthentication() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().is3xxRedirection()); //login form redirect
}
#Test
#WithMockUser(username="example", password="password", roles={"ANONYMOUS"})
public void adminTestWithBadAuthentication() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().isForbidden());
}
#Test
#WithMockUser(username="user", password="password", roles={"ADMIN"})
public void adminTestWithAuthentication() throws Exception {
mockMvc.perform(get("/admin"))
.andExpect(status().isOk())
.andExpect(view().name("admin"))
.andExpect(model().attributeExists("name"))
.andExpect(model().attribute("name", is("user")));
}
}
Tests fail because they are using the default security settings of Spring Boot.
I can fix this using #SpringBootTest + #AutoConfigureMockMvc, but it would be interesting to test without running all auto-configuration.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
public class ExampleControllerSpringBootTest {
#Autowired
private MockMvc mockMvc;
// tests
}
Is there any way that #WebMvcTest can use settings defined in SecurityConfig class?
WebMvcTest is only going to load your controller and nothing else (that's why we call that slicing). We can't figure out which part of your configuration you want and which one you don't. If the security config isn't on your main #SpringBootApplication you'll have to import it explicitly. Otherwise, Spring Boot is going to enable default security settings.
If you're using something like OAuth, that's a good thing though because you really don't want to start using that for a mock test. What happens if you add #Import(SecurityConfig.class) to your test?
Related
I have a multi module project, I noticed that when I run my tests (for example the tests annotated with #WebMvcTest) I get this message
Using generated security password: 12e4c462-385v-12y6-917u-e8u5rte36ooi
This generated password is for development use only. Your security configuration must be updated before running your application in production.
How do i remove it?
I think the "problem" is just that having a multi module project, in some tests, the class implementing UserDetailsService is not found because it is part of a different module and therefore the package is not scanned.
Is it enough for me to just ignore the message?
Actually this didn't happen before, it has happened since I removed a bean, probably useless, inside the WebSecuriyConfig class which extends the WebSecurityConfigurerAdapter.
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Since I don't use that bean anywhere in my application.
Security configuration is not loaded by default by #WebMvcTest. You need to manually #Import your web-security config and then setup test security-context.
For configuring OAuth2 test security context, you can use either
MockMvc request post-processor: jwt() or opaqueToken() from org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors)
a test annotation from this repo
Sample with #WithMockJwtAuth
#WebMvcTest()
#Import({ WebSecurityConfig.class })
class GreetingControllerTest {
#MockBean
JwtDecoder jwtDecoder;
#Autowired
MockMvc mockMvc;
#Test
#WithMockJwtAuth(authorities = {"NICE", "AUTHOR"}, claims = #OpenIdClaims(preferred_username = "Tonton Pirate"))
void whenGrantedNiceRoleThenOk() throws Exception {
mockMvc.perform(get("/greet")).andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE, AUTHOR]."));
}
#Test
#WithMockJwtAuth(authorities = {"AUTHOR"}, claims = #OpenIdClaims(preferred_username = "Tonton Pirate"))
void whenNotGrantedNiceRoleThenForbidden() throws Exception {
mockMvc.perform(get("/greet")).andExpect(status().isForbidden());
}
#Test
void whenAnonymousThenUnauthorized() throws Exception {
mockMvc.perform(get("/greet")).andExpect(status().isUnauthorized());
}
}
Same sample with jwt post-processor
#WebMvcTest()
#Import({ WebSecurityConfig.class })
class GreetingControllerTest {
#MockBean
JwtDecoder jwtDecoder;
#Autowired
MockMvc mockMvc;
#Test
void whenGrantedNiceRoleThenOk() throws Exception {
mockMvc.perform(get("/greet").with(jwt().jwt(jwt -> {
jwt.claim("preferred_username", "Tonton Pirate");
}).authorities(List.of(new SimpleGrantedAuthority("NICE"), new SimpleGrantedAuthority("AUTHOR"))))).andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE, AUTHOR]."));
}
#Test
void whenNotGrantedNiceRoleThenForbidden() throws Exception {
mockMvc.perform(get("/greet").with(jwt().jwt(jwt -> {
jwt.claim("preferred_username", "Tonton Pirate");
}).authorities(List.of(new SimpleGrantedAuthority("AUTHOR"))))).andExpect(status().isForbidden());
}
#Test
void whenAnonymousThenUnauthorized() throws Exception {
mockMvc.perform(get("/greet")).andExpect(status().isUnauthorized());
}
}
Spring Boot here. I currently have the following REST controller:
#RestController
public class FizzbuzzController {
private final FizzbuzzService FizzbuzzService;
public FizzbuzzController(FizzbuzzService FizzbuzzService) {
this.FizzbuzzService = FizzbuzzService;
}
#PostMapping("/Fizzbuzzs/{fizzbuzzId}")
public ResponseEntity<FizzbuzzDTO> addFizzbuzz(#RequestParam("files") List<MultipartFile> files,
#PathVariable String fizzbuzzId) throws IOException {
FizzbuzzDTO fizzbuzzDTO = fizzbuzzService.store(files, fizzbuzzId);
return ResponseEntity.status(HttpStatus.OK).body(fizzbuzzDTO);
}
}
I would like to write an integration test for it that:
Mocks or stubs an HTTP request to the URL; and
Allows me to inject the FizzbuzzController (under test) with a mocked FizzbuzzService or the real thing; and
Allows me to inspect the HTTP response coming back from the method (check status code, check response entity, etc.)
My best attempt thus far:
#WebMvcTest(FizzbuzzController.class)
#EnableConfigurationProperties
#AutoConfigureMockMvc
public class FizzbuzzControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private FizzbuzzService FizzbuzzService;
#Test
public void should_store_fizzbuzz_files() throws Exception {
// I can't even get the test to run
assertTrue(1 == 1);
}
}
When I run this, the test fails to run and it is clear (looking at the logs) that Spring is loading the entire application context of my app, whereas I just want it to isolate the context to this test class, the main FizzbuzzController class, and anything in the dependency tree underneath it.
Can anyone spot where I'm going awry?
You need another context for testing. I'm suggesting you to have a separate Test config:
#TestConfiguration
#Slf4j
#EnableJpaRepositories("tth.patientportal.repository")
public class TestConfig { // bean configs goes here for testing if you need to change
// context}
and in a controller test build the context like below:
#RunWith(SpringRunner.class)
#AutoConfigureTestEntityManager
#SpringBootTest
#TestPropertySource("classpath:application-unittest.properties")
#ContextConfiguration(classes = {TestConfig.class})
public class RestControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup()
{
mockMvc = MockMvcBuilders.
webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void shouldReturnRegisteredUser() throws Exception {
this.mockMvc.
perform(MockMvcRequestBuilders
.post("url")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.username").exists());
}
}
I've implemented BasicAuth in my SpringBoot application to authenticate few URLs. Somewhat like:
Configuration class
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${auth.authenticated}")
private String[] allAuthenticated;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(allAuthenticated).authenticated()
.and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) { 1
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
// .... Rest of the code
}
application.yml
auth.authenticated: /onlineshop/v1/ecart,/onlineshop/v1/wishlist
It is working fine, but I want to unit test this.
I was thinking of a simple test case where I can direclty make an HTTP request to either /onlineshop/v1/ecart OR /onlineshop/v1/wishlist and somehow check if they are authenticated or not.
I came across this MockMVC section, and coded below class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=WebSecurityConfig.class)
public class BasicAuthTest {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) 1
.build();
}
#Test
public void shouldBeAuthenticated() throws Exception {
mvc.perform(get("/onlineshop/v1/ecart").with(httpBasic("user","password"))).andExpect(status().isOk());
}
}
But every time it gives me 404 error. So I'm not sure if I've configured it properly or what?
Also if there is some other better way to test BasicAuth, kindly suggest.
Thank You
I'm new to spring security. Try to use it for project with a rest backend. For my backend certain urls need to be open, certain urls need to have httpbasic auth / https and certain urls need a token authentication.
I'm trying to set this up using a test with web mvc. Trying to test it by using controller methods:
#RequestMapping(value="/auth/signup", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void test(){
System.err.println("Controller reached!");
}
#RequestMapping(value="/auth/login", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void test2(){
System.err.println("Controller reached!");
}
My Spring Security Config locks like the following:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Configuration
#Order(1)
public static class FreeEndpointsConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/signup").permitAll()
.and().csrf().disable();
}
}
#Configuration
#Order(2)
public static class HttpBasicAuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/auth/login").hasAnyRole("USER")
.and().httpBasic()
.and().csrf().disable();
}
}
}
My Test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={RootContext.class, WebSecurityConfig.class})
#WebAppConfiguration
public class AccountSecurityTest {
#Autowired
private WebApplicationContext wac;
private MockMvc securityMockMvc;
#Before
public void SetupContext() {
securityMockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity()).build();
}
#Test
public void testSigInFree() throws Exception {
MockHttpServletRequestBuilder post = post("/auth/signup");
securityMockMvc.perform(post).andExpect(status().isOk());
}
#Test
public void testLoginHttpBasic() throws Exception {
MockHttpServletRequestBuilder post = post("/auth/login");
securityMockMvc.perform(post).andExpect(status().isOk());
}
}
The testmethod "testLoginHttpBasic" is green. But I would expect a failure, because i'm trying to configure / enforce httpbasic authentication. Where is my mistake?
Change
http.authorizeRequests().antMatchers("/auth/signup").permitAll()
to
http.antMatcher("/auth/signup").authorizeRequests().anyRequest().permitAll()
and
http.antMatcher("/auth/login").authorizeRequests().anyRequest().hasAnyRole("USER")
to
http.authorizeRequests().antMatchers("/auth/login").hasAnyRole("USER").
Your second test will fail.
Why do you need this change?
http.authorizeRequests()... creates a SecurityFilterChain that matches every URL. As soon as one SecurityFilterChain matches the request all subsequent SecurityFilterChains will never be evaluated. Hence, your FreeEndpointsConfig consumed every request.
With http.antMatcher("...") in place you restrict every SecurityFilterChain to a particular URL (pattern). Now FreeEndpointsConfig matches only /auth/signup and HttpBasicAuthConfig /auth/login.
Small improvement
You can make several URLs like paths to static resources (js, html or css) public available with WebSecurity::configure. Override WebSecurity::configure in your WebSecurityConfig
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring()
.antMatchers("/auth/signup");
}
and FreeEndpointsConfig isn't required anymore.
I have a working integration test for my Spring Web MVC app that looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = ShibaApplication.class)
#WebAppConfiguration
public class EchoControllerTests {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
private void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext).build();
}
#Test
public void echo() throws Exception {
mockMvc.perform(get("/echo/blargh"))
.andExpect(status().isOk())
.andExpect(content().string("blargh"));
}
}
Leaving that (successful) test in place, I tried to create an identical Cucumber test. The Cucumber runner is:
#RunWith(Cucumber.class)
#CucumberOptions(features="src/test/resources",
glue={"co.masslab.shiba", "cucumber.api.spring"})
public class CucumberTests {
}
The class that defines the Cucumber steps looks like:
#WebAppConfiguration
#Import(ShibaApplication.class)
#ContextConfiguration(classes=CucumberTests.class)
public class WebStepDefs {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
private ResultActions resultActions;
#When("^the client calls the echo endpoint$")
public void the_client_calls() throws Exception {
Assert.notNull(webApplicationContext);
this.mockMvc = webAppContextSetup(webApplicationContext).build();
this.resultActions = mockMvc.perform(get("/echo/blargh"));
}
#Then("^the client receives a status code of 200$")
public void the_client_receives_a_status_code() throws Exception {
resultActions.andExpect(status().isOk());
}
}
However, the cucumber test fails, as the result is not a 200 but a 404.
I suspect this is because the WebApplicationContext getting autowired into the WebStepDefs class isn’t the same as the one that gets autowired into the EchoControllerTests. I’ve been going over the Spring JavaConfig Reference Guide v1.0.0.M4, but I haven’t yet figured out where I’m going wrong.
I kept trying different combinations of annotations, and finally figured this one out. The annotations for WebStepsDef that worked for me were:
#ContextConfiguration(classes=ShibaApplication.class, loader=SpringApplicationContextLoader.class)
#IntegrationTest
#WebAppConfiguration