I'm having some trouble testing a Spring Boot application with MockMvc.
I have the following test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {SpringConfiguration.class, SecurityConfiguration.class})
#IntegrationTest({"server.port=8080"})
#WebAppConfiguration
public class DemoTest {
#Autowired
private EmbeddedWebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void testGetAccountUnauthenticated() throws Exception {
mockMvc.perform(get("/accounts/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isUnauthorized());
}
}
This results in a HTTP 200 not a 401. I have component scanning and autoconfiguration enabled and spring security is configured in my SecuityConfiguration class as follows:
#Configuration
#EnableWebSecurity
#EnableWebMvcSecurity // required for use of #AuthenticationPrincipal in MVC controllers.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) {
web.debug(true);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//set up authentication.
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
// set up form login
}
}
If I use a RestTemplate to access http://localhost:8080/accounts/1 then I get the expected behaviour (HTTP 401).
I have seen other examples (e.g. Spring Boot setup security for testing) that suggest that I autowire the FilterChainProxy and add the filter manually using the WebApplicationContext.addFilters(filterChainProxy) method. However, this actually fails for me (org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.web.FilterChainProxy] found).
I have two questions:
Why does the injected WebApplicationContext not automatically use the SpringSecurity filters? Even if I could get the FilterChainProxy and add it manually, the JavaDoc for EmbeddedWebApplicationContext states
any {#link Servlet} or {#link Filter} beans defined in the context will be automatically registered with the embedded Servlet container
As a result I wouldn't expect to have to manually add the security filter chain since I (incorrectly?) expect this to "just work" due to the Auto Configuration magic in Spring Boot?
Why is there no FilterChainProxy in the application context? Again, perhaps my expectations of the AutoConfiguration is incorrect - but I thought that this would be configured as part of the context configuration.
Thanks in advance for any advice.
Edits
The reason a FilterChainProxy doesn't get injected was because I has my configuration set to
public void configure(WebSecurity web) {
web.debug(true);
}
This actually configures a org.springframework.security.web.debug.DebugFilter instead. The way I have now managed to get the Filter regardless of this debug setting is as follows:
#Resource(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
private Filter securityFilter;
If I add this to the MockMvcBuilder as follows:
MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(securityFilter)
then it does work as expected.
But, I don't understand why MockMVC would ignore the filters as this seems important for testing a request since anything could happen in a Filter that might impact the outcome of the test. Furthermore, it means that to test properly I'd need to lookup all Filters in the servlet context and establish their priority/url mapping and add them appropriately. This seems error prone and unnecessary.
I agree that MockMVC is perhaps more for testing SpringMVC and custom code in controllers, as commented by #dave-syer. So in cases when one wants to test spring MVC infrastructure with your custom controller code at the same time (correctness of controllers mapped to URLs; mapping and validation of input and output objects; standard controllers; your controllers) without leveraging the Servlet container part of the stack, MockMVC is there for you.
But MockMVC also does have methods to add filters, so it is designed with a possibility to engage Filters in the described type of testing. Sometimes filter may play functional role for code inside of a controller and that would be otherwise not testable with MockMVC.
With all that theory in mind I was trying to mimic Boot behaviour for my tests where filters would be set up in Spring Boot way and picked up by my tests to be used with MockVMC. Here is a snippet that I ended up using. It can surely be enhanced to mimic Boot behaviour in more precisely and extracted to some custom MockMVCBuilder.
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setUp() {
Collection<Filter> filterCollection = wac.getBeansOfType(Filter.class).values();
Filter[] filters = filterCollection.toArray(new Filter[filterCollection.size()]);
mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilters(filters).build();
}
Have you tried this?
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
...
#Slf4j
#RunWith(SpringRunner.class)
#SpringBootTest
public class AuthorizeTest {
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity())
.build();
}
...
}
In my case it is 403, not 401, but you get the idea.
Related
With Spring Security <5.2
In a legacy project, for a password grant scenario, I need to configure an authorization server.
Currently it is done extending AuthorizationServerConfigurerAdapter, and the authorization endpoints are configured overriding the configure(AuthorizationEndpointsServerConfigurer) method.
My problem is that this configurer takes one AuthenticationManager for the password grant, when I would need something like an AuthenticationManagerResolver (but I can't upgrade to 5.2) to be able to apply a different authentication depending on the incoming URL (an authentication manager for admin URLs, e.g. "/admin/**", and another one for non-admin).
How can I do that? I can change the approach, but again I can't upgrade.
You can try implementing your own DelegatingAuthenticationManager, inject the list of your AuthenticationManagers in it, and put your logic in authenticate method. E.g:
#Component("delegatingAM")
public class DelegatingAuthenticationManager implements AuthenticationManager {
private final List<AuthenticationManager> ams;
#Autowire
public DelegatingAuthenticationManager(#Qualifier("x") AuthenticationManager amX, #Qualifier("y") AuthenticationManager amY) {
this.ams = List.of(amX, amY); // Arrays.asList(amX, amY);
// you can inject variables for your conditions here
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (...) this.ams.get(0).authenticate(authentication);
if (...) this.ams.get(0).authenticate(authentication);
// Or you can loop over the list like AuthenticationManager is implemented with AuthenticatioProvider
}
}
Then inject it to AuthorizationServerConfigurerAdapter
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("delegatingAM")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(this.authenticationManager)
}
...
}
Hope it will help, for the worse case, you could start to think about using many AuthorizationServerSecurityFilterChains, one AuthenticationManager for each. And based on the URL, direct the request to the right SecurityFilterChain.
I want to write some kind of unit test which depends on Spring Security.
For example, I have some service method which uses some repository and marked with #PreAuthorize annotation. Repository I can mock with Mockito, there is no problem. Also I can mock Security Context by #WithSecurityContext annotation. But when I run test, the #PreAuthorize annotation is just ignored. Of course I can run that test with #SpringBootTest annotation as an integration test and in this case the Security Context is up but this way is heavy and slow.
Is there a way to run unit test with only Spring Security Context raised?
UPDATE
Made an example of such kind of test. Thanks to #Sam Brannen for giving right direction.
#ActiveProfiles("method-security-test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ExampleService.class, ExampleServiceTest.MethodSecurityConfiguration.class})
public class ExampleServiceTest {
private ExampleService service;
#Autowired
public void setService(ExampleService service) {
this.service = service;
}
#Test
#WithMockUser(username = "john_doe")
public void testAuthenticated() {
String actualMessage = service.example();
Assert.assertEquals("Message of john_doe", actualMessage);
}
#Test(expected = AuthenticationException.class)
public void testNotAuthenticated() {
service.example();
Assert.fail();
}
#TestConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true)
static class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
}
}
#Service
class ExampleService {
#PreAuthorize("isAuthenticated()")
String example() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
return "Message of " + principal.getName();
}
The #PreAuthorize annotation from Spring Security will only be honored if Spring Security proxies your component (e.g., service bean).
The simplest way to make that happen is by annotating an #Configuration class with #EnableGlobalMethodSecurity(prePostEnabled = true), having your component registered as a bean (e.g., via component scanning or an #Bean method), and including your AuthenticationManager setup.
You can then create a focused integration test using #ContextConfiguration (without Spring Boot testing support) to load an ApplicationContext from your #Configuration class. And you can use #Autowired to get access to your proxied component which will be advised with the #PreAuthorize security check support.
You might find this old blog post useful as well for background information: https://spring.io/blog/2013/07/04/spring-security-java-config-preview-method-security/
What is the best way to run Spring Boot integration tests agains a OAuth Resource server configured web application.
I can think of two theoretical approaches:
Mock the security context in the resource server without acutally calling the Authorization server.
Embed the Authorization server as part of the test and redirect the authentication to it.
I was wondering how others have approach this problem.
This answer is very similar to the one provided by Ondrej, but is quite a bit simpler.
Spring Security 4 provides Test support. To use it ensure you have spring-security-test-4.0.2.RELEASE.jar (or newer version on your classpath). You will also want to ensure you are working with spring-test-4.1.0.RELEASE (or newer).
Next you can use MockMvc as the other answer indicates. However, if you setup MockMvc with the following:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class OAuthTests {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
// ADD THIS!!
.apply(springSecurity())
.build();
}
This makes it so
You no longer need to worry about running in stateless mode or not
It also means you do not need to use apply(springSecurity()) as indicated in the other answer.
In short, you should be able to do something like this:
#Test
#WithSecurityContext('user')
public void performOAuth() throws Exception {
...
// No need for apply(security())!!
restParcelMockMvc.perform(get("/api/some-resource"))
.andExpect(...);
}
I'd encourage you to read through the rest of the Spring Security Testing section of the reference as it provides lots of additional details including how to use custom authentication.
I use spring security 4.x #WithSecurityContext('user') annotation to create mock SecurityContext with 'user' logged in. Then when calling my REST API using MockMvc I retrieve SecurityContext and attach it to the call.
Like this:
#Test
#Transactional
#WithSecurityContext('user')
public void getAllParcels() throws Exception {
// Initialize the database
long size = parcelRepository.count();
parcelRepository.saveAndFlush(parcel);
// Get all the parcels
restParcelMockMvc.perform(get("/api/parcels").with(security()))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[" + size + "].id").value(parcel.getId()))
.andExpect(jsonPath("$.[" + size + "].lot").value(DEFAULT_LOT))
.andExpect(jsonPath("$.[" + size + "].localName").value(DEFAULT_LOCAL_NAME));
}
where security() is static method:
public static RequestPostProcessor security() {
return SecurityMockMvcRequestPostProcessors.securityContext(SecurityContextHolder.getContext());
}
So using #WithSecurityContext('user') mock SecurityContext with authenticated user with login 'user' is created for my test method. Then in that method I retrieve this mock SecurityContext and attach it to the REST API call to make my oAuth think user is allready authenticated. It's basically the first approach you suggested in your question.
For this to work you must switch your OAuth to be statefull for the tests. Otherwise it won't work.
ie like this:
#Configuration
public class OAuth2ServerConfiguration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired(required = false)
#Qualifier("oauth2StatelessSecurityContext")
private Boolean stateless = Boolean.TRUE; // STATEFUL switching for tests!
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/logs/**").hasAnyAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(stateless);
super.configure(resources);
}
}
...
You see my stateless property which gets injected only in tests. In normal run it uses it's default value true (so it's stateless). For tests I declare oauth2StatelessSecurityContext Bean with value false so it turns statefull for tests.
I define this configuration for tests:
#Configuration
public class OAuth2Statefull {
#Bean
#Primary
public Boolean oauth2StatelessSecurityContext() {
return Boolean.FALSE;
}
}
That's how I did it. I hope my explanation is understandable.
I have a rest webservice configured as a spring boot application.
All my rest urls have a base path "/api/...".
I am also serving static content from my application.
I need to configure security ONLY for the web service i.e., URLs that start with "/api/..." but give the other static content w/o applying security.
I've only seen examples where we filter some url patterns via:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/*");
}
but not otherwise ...
Use the antMatcher method of HttpSecurity class:
#Configuration
#EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**");
// add security constraints for /api/... here
}
/* rest of config */
}
Instead of antMatcher, you can you regexMatcher wich can be a negation pattern
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().regexMatchers(XXXXX);
}
Answer to your last comment, if you are using latest spring framework and spring security then define below class and security config as per the config standards.
package org.springframework.security.samples.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class MessageSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
Also, look at below URL if you still find it difficult to get started with spring security.
http://docs.spring.io/spring-security/site/docs/3.2.6.RELEASE/reference/htmlsingle/#hello-web-security-java-configuration
I am trying to implement a Spring Boot version of Pet Clinic overriding as little of Spring Boot defaults as possible.
At this point, my logout link doesn't seem to work and I've been told that it is because I haven't properly added HttpSessionEventPublisher to my application.
How can I add HttpSessionEventPublisher to my application?
I've tried the following:
#Component
#Configuration
public class WebXmlConfig implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new HttpSessionEventPublisher());
}
}
and
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> getHttpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
My main class does not extend any classes. If that is required for me to add HttpSessionEventPublisher, I would need to know which class also.
None of the Spring Boot examples log out properly, so I have nothing to based on off of.
Any help would be appreciated.