How can I provide a Spring configuation bean in a WebSecurityConfigurerAdapter for testing? - spring

I use the following SecurityConfiguration class for securing endpoints in a Spring Boot application. This class depends on the ApiConfiguration class which provides the username and password for the in-memory authentication.
When starting a #WebMvcTest for a controller, then Spring also tries to initialize the security configuration but fails to load the application context.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'ApiConfiguration' available: expected at least 1 bean which qualifies as autowire candidate.
I tried adding a MockBean to the test class:
#MockBean
private ApiConfiguration apiConfiguration;
This resolves the above issue, but then the username and password are null.
Is there any Spring support for providing this configuration bean for testing?
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Configuration
#Order(1)
public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String API_USER_ROLE = "API_USER";
private final ApiConfiguration apiConfig;
public ApiSecurityConfiguration(ApiConfiguration apiConfig) {
this.apiConfig = apiConfig;
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(apiConfig.getUsername())
.password(passwordEncoder().encode(apiConfig.getPassword()))
.roles(API_USER_ROLE);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.formLogin().disable()
.mvcMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasRole(API_USER_ROLE)
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
}

I ended up creating a CustomApiConfiguration annotated with #TestConfiguration. This seems to do the trick. At least, Spring is able to read the properties when setting up the SecurityConfiguration class.
#TestConfiguration
public class CustomApiConfiguration {
#Bean
public ApiConfiguration apiConfiguration() {
final ApiConfiguration config = new ApiConfiguration();
config.setUsername("api-username");
config.setPassword("api-password");
return config;
}
}

Related

#Value annotation return empty value in an AbstractAuthenticationProcessingFilter filter

I'm developing a springboot application with spring security.
I'm trying to make my custom authentication filter reading some properties from the application.properties file without success.
I've read this other question which is similar but within a different context (not related to spring security filters). The reason for the failure makes sense to me but I've tried the way suggested with the DelegatingFilterProxy but without success (to be fair, I didn't really get the meaning of the part added to the Application class). The other solution does not fit my case as I don't have any onStartup method to override.
Here is the code I'm using:
public class JWTAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
#Value("${app.jwtSecret}")
public String SECRET2;
Almost the same code, in a controller class, works fine:
#RestController
#RequestMapping("/api")
#CrossOrigin
#EnableAutoConfiguration
public class UsersController {
#Value("${app.jwtSecret}")
public String SECRET2;
But I can't make it work in the filter. I'm using springboot 2.0.3.
Any suggestion? Is the DelegatingFilterProxy the right approach in this situation? In that case, any example/article I could follow?
Thanks,
Michele.
UPDATE:
to fully answer to the first comment, the filter is called by the following class:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private LdapAuthenticationProvider ldapAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/secureLogin").permitAll()
.antMatchers(HttpMethod.GET, "/api").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
No need to use #Value in filter class:
public class JWTAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
private String secret;
//... setter for secret
But inject the secret in the config class:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Value("${app.jwtSecret}")
public String secret;
//...
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthorizationFilter jwtFilter = new JWTAuthorizationFilter(authenticationManager());
//set secret
//...
}

Confused about Spring boot test specific #TestConfiguration

According to Spring Boot Docs, a nested #TestConfiguration should be detected by tests automatically.
But in my test codes it is problematic when I ran the whole test class, it was not detected even I added it explicitly by #Import. The test code structure is like the following:
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#RunWith(SpringRunner.class)
//#Import(IntegrationTests.TestSecurityConfig.class)
public class IntegrationTests {
// test methods
// test configuration
#TestConfiguration
static class TestSecurityConfig {}
}
When I ran single test cases(test methods) individually, all tests are passed as expected, but when I ran the test class directly, some tests are failed, the #TestConfiguration was not applied to test.
The complete codes of this IntegrationTests is here.
UPDATE:
A workaround added in my codes to make the tests passed.
#TestComponent
#Slf4j
static class TestUserDetailsService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
TestUserDetailsService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = User.withUsername("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder.encode("password"))
.roles("ADMIN")
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
log.debug("dummy user:" + user);
log.debug("dummy admin:" + admin);
if ("user".equals(username)) {
return user;
} else {
return admin;
}
}
}
#TestConfiguration
#Slf4j
#Import(TestUserDetailsService.class)
#Order(-1)
static class TestSecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
PasswordEncoder passwordEncoder;
#Inject
UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/posts/**").permitAll()
.antMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
}
There are still some things confused me.
In the test class, why the #TestConfiguration can not detect #TestComponent located in the same test, I have to add #Import to fix it.
As described in the security section of Spring Boot Docs, I was thinking defining a UserDetailsService bean is enough, it will serve the users in security, but it did not work in tests. I have to configure a WebSecurityConfigurerAdapter and expose AuthenticationManager for test, why? And more confused me is as described before , running the tests one by one is ok if there is no WebSecurityConfigurerAdapter defined for test.
The #TestConfiguration annotated WebSecurityConfigurerAdapter does not get a higer order, I have to add #Order on it. I was thinking a #TestConfiguration bean should get Primary automatically and replace the bean in my application config, right?
Simply adding #Order(Ordered.HIGHEST_PRECEDENCE) solved it in my case:
#Order(Ordered.HIGHEST_PRECEDENCE)
#TestConfiguration
I'm not quite sure why that isn't the default. My #ConditionalOnBean was evaluated before that #TestConfiguration was actually initializing those beans.

required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found

When launching with mvn spring-boot:run or even with gradle returns that issue.
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userDetailsService in webroot.websrv.auth.config.WebSecurityConfiguration required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' in your configuration.
BUILD SUCCESSFUL
Total time: 19.013 secs
Here are the main classes, all the requirements looks ok to me, I am using the org.springframework.boot release 1.5.7.RELEASE
package webroot.websrv.auth.config;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(
HttpMethod.GET,
"/",
"/**/*.html",
"/**/*.{png,jpg,jpeg,svg.ico}",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
}
}
and:
package webroot.websrv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class WebcliApplication {
public static void main(String[] args) {
SpringApplication.run(WebcliApplication.class, args);
}
}
Using Maven or Gradle it returns the same issue. All annotations and packages names seems to be as required.
Add a bean for UserDetailsService
#Autowired
private UserDetailsService userDetailsService;
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
I also come accross this error. In my case, I have a class JwtUserDetailsService and I have forget implement UserDetailsService. After adding implements UserDetailsService the error was disappered.
Note that: if you also have own UserDetailsService and you use Munna's answer, than you got error StackoverflowError it mean you also repited my mistake.
In Service class make annotation
#Service
to
#Service("userDetailsService")

SpringBoot + method based hierarchical roles security: ServletContext is required

I added method-based security and added role hierarchy. I keep getting the following exception during build:
org.springframework.beans.BeanInstantiationException: Failed to
instantiate [org.springframework.web.servlet.HandlerMapping]: Factory
method 'defaultServletHandlerMapping' threw exception; nested
exception is java.lang.IllegalArgumentException: A ServletContext is
required to configure default servlet handling
I tried a lot of different configuration alternatives and none works...
Here is my basic web security configuration class (I added role hierarchy-related beans as you see):
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
(...)
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf()
.disable()
(...)
.expressionHandler(webExpressionHandler())
(...)
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
return defaultWebSecurityExpressionHandler;
}
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy(Role.getHierarchy());
return r;
}
(...)
}
Here is the separate config file that I created basing on this thread:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private RoleHierarchy roleHierarchy;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = (DefaultMethodSecurityExpressionHandler) super.createExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
}
I struggled with this for many hours. The related topics I tried are here:
Spring Boot + Spring Security + Hierarchical Roles
Error creating bean with name 'defaultServletHandlerMapping
Error creating bean with name defaultServletHandlerMapping
Every help appreciated!
OK, found it.
I played around with the class annotations and came to a simple solution: I removed #EnableGlobalMethodSecurity from GlobalMethodSecurityConfig and moved it to WebSecurityConfiguration, where it was before. So it looks like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { (...) }
and this:
#Configuration
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration { (...) }
The most interesting part is that the accepted answer from here advises what didn't work for me.

Spring Security PreAuthorize Custom Method Bean resolver is not registered?

Im just learning Spring, going through tutorials and testing possibilities. One of my goals is to secure a Service Method using a Custom Method and the PreAuthorize annotation. Unfortunaly the Bean holding the custom Method cannot be resolved and I dont know why. Maybe someone can see the error at first sight.
Bean holding the custom Method:
#Component("mySecurityService")
public class MySecurityService {
public boolean hasPermission() {
return true; //simple implementation just to look if has permission is called
}
}
Service to be Secured:
public interface OrderService {
#PreAuthorize("#mySecurityService.hasPermission()")
public AllOrdersEvent requestAllOrders(RequestAllOrdersEvent requestAllCurrentOrdersEvent);
public OrderDetailsEvent requestOrderDetails(RequestOrderDetailsEvent requestOrderDetailsEvent);
public OrderStatusEvent requestOrderStatus(RequestOrderStatusEvent requestOrderStatusEvent);
public OrderCreatedEvent createOrder(CreateOrderEvent event);
public OrderUpdatedEvent setOrderPayment(SetOrderPaymentEvent setOrderPaymentEvent);
public OrderDeletedEvent deleteOrder(DeleteOrderEvent deleteOrderEvent);
}
Java Security Config:
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("letsnosh").password("noshing").roles("USER");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean(name = "mySecurityService")
MySecurityService createSecurityService(){return new MySecurityService();}
#Override
protected void configure(HttpSecurity http) throws Exception {
/*
http.authorizeUrls()
.antMatchers("/aggregators*//**//**").hasRole("USER")
.anyRequest().anonymous()
.and()
.httpBasic();
*/
}
}
Error:
No bean resolver registered in the context to resolve access to bean 'mySecurityService'
Hello I solved the problem. It was connected to the Version of Spring Security.
I got the Version from the official Spring Rest Tutotrial : 3.2.0.M2
In this version I had to declare the Security Context as follows:
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("letsnosh").password("noshing").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeUrls()
.antMatchers("/aggregators/**").hasRole("USER")
.anyRequest().anonymous()
.and()
.httpBasic();
}
}
Here the error was thrown.
But using a newer Version of Spring Security: 3.2.5.RELEASE
I could declare the Config this way:
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeUrls()
.antMatchers("/aggregators*//**//**").hasRole("USER")
.anyRequest().anonymous()
.and()
.httpBasic();
}
And the bean could be resolved, using either #Component Annotaions directly on the MySecurityService class or #Bean annotations on a config class method which returns a MySecurityService instance.

Resources