Today I updated my project from Spring Boot 1.5.9 to 2.1.1, and some of my tests stopped working. When i start the tests, error pops on console:
Field authEntryPoint in com.example.rest.config.SecurityConfig required a bean of type 'com.example.rest.service.auth.entrypoints.AuthenticationEntryPoint' that could not be found.
The problem is I have bean of this type defined in my SecurityConfig class, but I am overriding this configuration in my test package in TestApplication class. Security config is defined there as static inner class. I have tried different approaches including Spring profiles and #Primary annotation, but nothing seems to work and Spring doesn't pick my test configuration like it did before. Only thing that worked was when I deleted the non-test version of SecurityConfig class and test version became only bean of this type.
Can someone tell me how do I override this original configuration or how to turn off Spring Security just for testing? Or maybe there is a way to force Spring not to pick up that non-test #Configuration bean?
SecurityConfig.class
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationEntryPoint authEntryPoint;
#Autowired
BasicAuthenticationProvider basicAuthProvider;
#Autowired
PreAuthenticatedUserDetailsService preAuthUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/rest/query/id/*/user/*",
"/rest/files/**/*").hasAnyRole("CLIENT", "SYSTEM")
.antMatchers("/public/api/management/**/*").hasRole("SYSTEM")
.antMatchers("/public/api/**/*").hasAnyRole("SYSTEM", "USER")
.antMatchers("/rest/**/*").hasRole("SYSTEM")
.and()
.x509()
.userDetailsService(preAuthUserDetailsService)
.and()
.httpBasic()
.authenticationEntryPoint(authEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(basicAuthProvider);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/").antMatchers("/rest/files/name/**");
}
}
Test SpringBootClass with SecurityConfig inside
#SpringBootApplication
public class TestApplication {
#Configuration
#EnableWebSecurity
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll()
.and().csrf().disable();
}
}
}
Example test from the suite
#RunWith(SpringRunner.class)
#WebMvcTest(DocumentManagementController.class)
public class DocumentManagementControllerTests {
#Autowired
MockMvc mvc;
#MockBean
SystemMetadataService systemMetadataService;
#MockBean
CustomMetadataService customMetadataService;
#MockBean
PrinterService printerService;
#MockBean
EventLoggerService eventLoggerService;
#Captor ArgumentCaptor<String> systemCaptor;
#Captor ArgumentCaptor<String> clientCaptor;
#Captor ArgumentCaptor<Boolean> holdCaptor;
#Captor ArgumentCaptor<String> retentionCaptor;
#Captor ArgumentCaptor<String> objectPathCaptor;
#Captor ArgumentCaptor<Boolean> accessCaptor;
#Captor ArgumentCaptor<Boolean> manualProcessingCaptor;
#Captor ArgumentCaptor<Boolean> incorrectCaptor;
#Captor ArgumentCaptor<Integer> statusCaptor;
#Captor ArgumentCaptor<Boolean> noTemplateCaptor;
#Test
public void setDocumentAccess_givenProperData_shouldReturnOk() throws Exception {
when(customMetadataService.setDocumentAccess(anyString(), anyBoolean()))
.then(inv -> new HcpCreateObjectResult(inv.getArgument(0)));
Boolean accessForbidden = true; String objectPath = "path";
mvc.perform(get("/rest/management/access/forbid/"+accessForbidden+"?objectPath="+objectPath))
.andExpect(status().isOk());
verify(customMetadataService).setDocumentAccess(objectPathCaptor.capture(), accessCaptor.capture());
assertThat(objectPathCaptor.getValue(), is(equalTo(objectPath)));
assertThat(accessCaptor.getValue(), is(equalTo(accessForbidden)));
}
I managed to do make this work using #Profile and #ActiveProfiles. But i had to extract my static inner #Configuration class to another java file and then it automagically started to work. Still haven't found why it worked in earlier version of Spring Boot
Related
In my Spring Boot app, I'm trying to remove the usage of the deprecated class WebSecurityConfigurerAdapter for configuring security, as recommended in this blog post.
Previously, my security configuration class looked like this:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// the filter classes constructor arguments are
// #Autowired fields of SecurityConfiguration
var jwtAuthenticationFilter = new JwtAuthenticationFilter(
authenticationManager(),
authenticationAuditService,
jwtTokenService,
roleResolver,
objectMapper,
messageSourceAccessor,
roleActivityService);
var jwtAuthorisationFilter = new JwtAuthorisationFilter(jwtTokenService, userRepository, userRoleRepository);
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class)
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
All this really does is create 2 Spring Security filters and adds them to the filter chain. Following the removal of WebSecurityConfigurerAdapter, the class now looks like this:
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
// the filter classes constructor arguments are
// #Autowired fields of SecurityConfiguration
var jwtAuthenticationFilter = new JwtAuthenticationFilter(
authenticationConfiguration.getAuthenticationManager(),
authenticationAuditService,
jwtTokenService,
roleResolver,
objectMapper,
messageSourceAccessor,
roleActivityService);
var jwtAuthorisationFilter = new JwtAuthorisationFilter(jwtTokenService, userRepository, userRoleRepository);
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class)
.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
However, following these changes a bunch of tests are failing. It seems that the reason for this is because the security filters are no longer loaded. For example, the following test passed before making the changes above
#WebMvcTest(MyController.class)
#WithMockUser
public class MyControllerTests {
#Autowired
private MyController controller;
#Test
void deleteRole_ShouldThrowsAccessDeniedException_WhenUserHasInvalidRole() {
assertThrows(AccessDeniedException.class, () -> this.controller.deleteRole(UUID.randomUUID()));
}
}
The controller method/action that is being tested is:
#PreAuthorize("hasAuthority('SYSTEM_ADMIN')")
public void deleteRole(#PathVariable UUID id) {
// implementation omitted
}
It appears the test is now failing because the security filters required to implement the role-checking defined by #PreAuthorize are not present.
Is there a way to force the security configuration to be loaded when the tests are run?
You need to annotate your test with
#Import(SecurityConfiguration.class)
The filter for the context slice of #WebMvcTest automatically includes implementations of WebSecurityConfigurerAdapter that are annotated with #Component but it doesn't scan #Bean factory methods of configuration classes.
This is also noted in the release notes for Spring Boot 2.7. There are more details on the discussion here: https://github.com/spring-projects/spring-boot/issues/31162
Below is my test class. The hello-world endpoint simply returns an HTML page containing text i.e. Hello Stranger!
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloWorldTest {
#Autowired
private HelloWorldController controller;
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
private int port;
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
String baseUrl = "http://localhost:" + port;
assertThat(this.restTemplate.getForObject(baseUrl+"/hello-world", String.class))
.contains("Hello Stranger!");
}
}
This is my Security Config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
It simply redirects all authenticated users to the login page
I have tried adding #WithMockUser annotation or adding another security config class in my test directory to override the default config. But so far nothing has seemed to work.
Any help or suggestions on documentation to read is appreciated!
Another way to do it that worked for me was to override the normal security configation for running the integration test like so:
#TestConfiguration
#Order(-101)
#EnableWebSecurity
class TestSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity security) throws Exception {
security.httpBasic().and().formLogin().disable();
}
}
I have managed to solve this issue by first creating another web security config without requiring login/authorization, then by adding #Profile to my config class and production/dev/test profile via application.properties in my test directory (i.e. adding "spring.profiles.active=test").
Not sure if this is the best way to solve this issue, but it works for now.
While attempting some tests using the Spring Boot framework, I am running into an issue with finding a Bean that the test unit depends on.
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authServerApplication': Unsatisfied dependency expressed through field 'passwordEncoder'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.password.PasswordEncoder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
My test class:
#RunWith(SpringRunner.class)
#DataJpaTest
#SpringBootTest
public class UserDetailsTest {
#Autowired
private TestEntityManager entityManager;
// #MockBean
// private PasswordEncoder passwordEncoder;
#Autowired
private UserRepository userRepo;
#Test
public void test() {
OAuthUser user = null;
this.entityManager.persist(new OAuthUser("Kelly", "Marchewa", "kmarchewa", "password"));
user = userRepo.findByUserName("kmarchewa");
System.out.println(user.getPassword());
assertThat(user.getUserName()).isEqualTo("kmarchewa");
}
}
If I uncomment the #MockBean portion, the code will compile fine. However, I want to test the repository on its ability to encode and decode passwords too. To my understanding of the documentation, the #SpringBootTest annotation should be able to automatically "pick-up" the #Configuration classes. I have a main #SpringBootApplication:
#SpringBootApplication
public class AuthServerApplication {
#Autowired
private PasswordEncoder passwordEncoder;
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
#Bean
public CommandLineRunner demo(UserRepository repository) {
return(args) -> {
OAuthUser user = new OAuthUser();
user.setFirstName("Kelly");
user.setLastName("Marchewa");
user.setPassword(passwordEncoder.encode("Admin"));
user.setUserName("Admin");
// repository.save(user);
};
}
}
This Spring Boot Application depends on three other #Configuration classes: AppConfig, SecurityConfig, and AuthServerConfig. For this issue, the SecurityConfig and AppConfig classes are relevant (they include references to the PasswordEncoder bean).
AppConfig (partial)
#Configuration
public class AppConfig {
#Value("${spring.datasource.url}")
private String datasourceUrl;
#Value("${spring.datasource.driverClassName}")
private String dbDriverClassName;
#Value("${spring.datasource.username}")
private String dbUsername;
#Value("${spring.datasource.password}")
private String dbPassword;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/// more code here
}
SecurityConfig:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuthUserDetailsService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// Hash password
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName("test")
.and()
.csrf()
.disable();
}
}
The UserRepository class is very simple:
public interface UserRepository extends CrudRepository<OAuthUser, Long> {
public OAuthUser findByUserName(String name);
}
How can I ensure all required beans are found for my tests?
Thanks.
EDIT:
I receive the same error if I attempt to #Autowire the bean in my test class.
#Autowired
private PasswordEncoder passwordEncoder;
The problem is #DataJpaTest this annotation should be used only for Data repositories test and not full integration (which is what you are doing) as because it only persistence beans are created in context and not all you beans (the reason bean can not be found). What you need to do is use only #SpringBootTest and declared h2 as testing dependency, in that way a full recreation of your application will be created using in memory database
I have a custom implementation of WebSecurityConfigurerAdapter where I override the config() method to authorize requests with matchers.
I need to create unit tests that use mock mvc to send requests to my controllers to make sure that they are being blocked properly. But when I run my tests, they don't load my implentation of WebSecurityConfigurerAdapter.
Overriden WebSecurityConfigurerAdapter::configure() method from my SecurityConfigSso.class:
#Override
protected void configure( HttpSecurity http ) throws Exception {
http.authorizeRequests()
.antMatchers( "/img/**", "lib/**", "/api/event**", "/api/event/**","/login/cas**" ).permitAll()
.antMatchers(HttpMethod.GET, "/**").hasAnyAuthority(AvailableRoles.ANY)
.antMatchers(HttpMethod.POST, "/**").hasAnyAuthority(AvailableRoles.ADMIN, AvailableRoles.GIS_ANALYST)
.antMatchers(HttpMethod.PUT, "/**").hasAnyAuthority(AvailableRoles.ADMIN, AvailableRoles.GIS_ANALYST)
.antMatchers(HttpMethod.DELETE, "/**").hasAnyAuthority(AvailableRoles.ADMIN, AvailableRoles.GIS_ANALYST)
.anyRequest().authenticated();
}
Here is my unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { SecurityConfigSso.class })
public class SecurityTestControllerTests {
private final String SECURITY_URL = "/security";
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Before
public void init() {
Assert.assertNotNull(context);
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void postMethodShouldBeForbiddenToGuest() throws Exception {
this.mockMvc.perform(post(SECURITY_URL).with(user("test").roles(AvailableRoles.GUEST)))
.andExpect(status().isForbidden()).andReturn();
}
}
The result of this test should be a 403 from the server, but it's still 200... :(
You need to add security to the mockMvc:
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity())
.build();
For an example, have a look at https://github.com/spring-projects/spring-security/blob/master/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/SecurityRequestsTests.java
I'm unable to configure correctly the security in my tests.
My web security configuration:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").hasRole("USER")
.and()
.httpBasic()
;
}
}
And my test class:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration
#ContextConfiguration(classes = {Application.class, AppConfig.class, WebMvcConfig.class, WebSecurityConfig.class})
#WebAppConfiguration
public class TestControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.mockMvc = webAppContextSetup(wac).dispatchOptions(true).build();
}
#Test
public void getTest() throws Exception {
mockMvc
.perform(get("/api/test"))
.andExpect(status().isForbidden())
;
}
}
I get a 404 status code meaning the security layer is not executed, so it is not configured correctly in my test class.
I tried to switch the classes from #ContextConfiguration to #SpringApplicationConfiguration without success.
Make the following modifications to your code:
#Autowired
private FilterChainProxy filterChainProxy;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.mockMvc = webAppContextSetup(wac).dispatchOptions(true).addFilters(filterChainProxy).build();
}
As said in reference for Spring Security 4.0.4:
In order to use Spring Security with Spring MVC Test it is necessary to add the Spring Security FilterChainProxy as a Filter. It is also necessary to add Spring Security’s TestSecurityContextHolderPostProcessor to support Running as a User in Spring MVC Test with Annotations. This can be done using Spring Security’s SecurityMockMvcConfigurers.springSecurity().
Example:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class TestControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity()) //will perform all of the initial setup to integrate Spring Security with Spring MVC Test
.build();
}