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

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")

Related

WebSecurityConfigurerAdapter is deprecated [duplicate]

I am trying to update the WebSecurityConfigurerAdapter as it has been deprecated. The class is configured as follows:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UsuariService userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/api/auth/**").permitAll().antMatchers("/api/test/**").permitAll().antMatchers("/api/v1/**").permitAll().anyRequest()
.authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Now without the WebSecurityConfigurerAdapter I redefine the same class like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Autowired
UsuariService userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Bean
AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) throws Exception {
return builder.userDetailsService(userDetailsService).passwordEncoder(encoder()).and().build();
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.antMatchers("/api/v1/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
But unfortunately I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration':
Unsatisfied dependency expressed through method 'setFilterChains' parameter 0;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'filterChain' defined in class path resource [cit/base/app/security/WebSecurityConfig.class]:
Unsatisfied dependency expressed through method 'filterChain' parameter 0;
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]:
Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception;
nested exception is java.lang.IllegalStateException:
Cannot apply org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer#3fdc705c to already built object
I would appreciate any kind of help that would be most welcome.
I have managed to update the methods. This is the WebSecurityConfig class, and the methods are modified in the following way:
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
has become:
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
Explanation: In the old version you inject AuthenticationManagerBuilder, set userDetailsService, passwordEncoder and build it. But authenticationManager is already created in this step. It is created the way we wanted (with userDetailsService and the passwordEncoder).
Next, the configure() method for HttpSecurity is replaced by filterChain method as it is explained on the official site: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter.
import com.myproject.UrlMapping;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final AuthEntryPointJwt unauthorizedHandler;
private final AuthTokenFilter authenticationJwtTokenFilter;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(UrlMapping.AUTH + UrlMapping.SIGN_UP).permitAll()
.antMatchers(UrlMapping.AUTH + UrlMapping.LOGIN).permitAll()
.antMatchers(UrlMapping.VALIDATE_JWT).permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
};
}
}
I really hope my answer helped you!
Also, I have added this in my build.gradle file:
implementation 'javax.xml.bind:jaxb-api:2.3.0'
I hope this configuration will work for UserDetailsService, AuthenticationManagerBuilder and AuthenticationManager.
#Configuration
public class BeanConfiguration {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
public class SpringSecurityConfiguration {
AuthenticationManager authenticationManager;
#Autowired
UserDetailsService userDetailsService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService);
authenticationManager = authenticationManagerBuilder.build();
http.csrf().disable().cors().disable().authorizeHttpRequests().antMatchers("/api/v1/account/register", "/api/v1/account/auth").permitAll()
.anyRequest().authenticated()
.and()
.authenticationManager(authenticationManager)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
#Component
class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private AccountService accountService;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Account account = accountService.findAccountByEmail(email);
return new UserPrincipalImp(account);
}
// ...
}
change your file like this :
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SpringSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().authorizeHttpRequests()
.requestMatchers("/user/register").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer();
return http.build();
}
}
The complete implementation of SecurityConfig class without extending the WebSecurityConfigurerAdapter is as follows.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
UserDetailsService userDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// don't authenticate this particular request
.authorizeHttpRequests().antMatchers("/authenticate").permitAll()
// all other requests need to be authenticated
.anyRequest().authenticated().and()
// make sure we use stateless session; session won't be used to
// store user's state.
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
It works in my case, the simplest way is to pass your userDetailService class directly in the SecurityFilterChain function.
Note :
http.userDetailsService(customUserDetailService);
BCryptPasswordEncoder class automicaly get autowired as password Encoder, if #Bean method is avilable in configration.
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Code :
package com.example.blogapi.config;
import com.example.blogapi.security.CustomUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Autowired
private CustomUserDetailService customUserDetailService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(
(authz) -> authz.anyRequest()
.authenticated())
.httpBasic(Customizer.withDefaults())
.userDetailsService(customUserDetailService);
return http.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
security config
#Configuration
public class SecurityConfig {
#Bean
public PasswordEncoder passwordEncoder() {
int rounds = 12;
return new BCryptPasswordEncoder(rounds);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.httpBasic()
.and()
.authorizeHttpRequests()
/*.requestMatchers("/user/**").hasRole("USER")*/
.requestMatchers("/user/**", "/user/info/**").hasAuthority("USER")
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);;
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(UserDetailsService customUserDetailsService) {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(customUserDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
List<AuthenticationProvider> providers = List.of(authProvider);
return new ProviderManager(providers);
}
}
service
#Service
#RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
private final CustomerRepository customerRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final CustomerModel customer = customerRepository.findByEmail(username); /*email*/
Set<UserRole> roles = new HashSet<>();
roles.add(new UserRole("USER"));
roles.add(new UserRole("ADMIN"));
if (customer == null) {
throw new UsernameNotFoundException(username);
}
String email = customer.email();
String password = customer.password();
return User
.withUsername(email)
.password(password)
/*.roles("USER")*/ /*Into a Security filter must be expression -> hasRole()*/
.authorities(convertAuthorities(roles))
.build();
}
private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authorities=new HashSet<>();
for (UserRole userRole : userRoles) {
authorities.add(new SimpleGrantedAuthority(userRole.nameRole()));
}
return authorities;
}
}
you modify your class like this:
#Configuration
#EnableWebSecurity
public class Securityconfiguration{
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withUsername("user1")
.password(passwordEncoder().encode("user1Pass"))
.roles("USER")
.build();
UserDetails manager = User.withUsername("user2")
.password(passwordEncoder().encode("user2Pass"))
.roles("MANAGER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("adminPass"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, manager, admin);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/index.html").permitAll()
.requestMatchers("/profile/**").authenticated()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/management/index").hasAnyRole("ADMIN","MANAGER")
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
}

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

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;
}
}

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.

How to Control Sequence of Bean Creating and Component Scanning In Spring Boot

Updated
I am Beginner to Spring and I tried to implement spring security application using Java Based Configuration. But now I have to Control sequence of bean creating and component scanning of application.
This is my configuration class
#EnableWebSecurity
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public Md5PasswordEncoder passwordEncoder() {
return new Md5PasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl();
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authProxy").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTLoginFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
and here is the JWTLoginFilter
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher("/login"));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
final Authentication authentication = getAuthenticationManager()
.authenticate(new UsernamePasswordAuthenticationToken(credentials.getUsername(),
credentials.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
String name = authentication.getName();
tokenAuthenticationService.addAuthentication(response, name);
}
}
This is working fine.
But all thing going wrong When I try to declare JWTLoginFilter as service with #Service annotation and while I am trying to Autowire that.
The Changes that I did as Follows.
this is configuration class.
#EnableWebSecurity
#Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public Md5PasswordEncoder passwordEncoder() {
return new Md5PasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Autowired
JWTLoginFilter jwtLoginFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl();
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authProxy").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
And this is my new JWTLoginFilter
#Service
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
AuthenticationManager authenticationManager;
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter() {
super(new AntPathRequestMatcher("/login"));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper().readValue(httpServletRequest.getInputStream(), AccountCredentials.class);
final Authentication authentication = getAuthenticationManager()
.authenticate(new UsernamePasswordAuthenticationToken(credentials.getUsername(),
credentials.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
String name = authentication.getName();
tokenAuthenticationService.addAuthentication(response, name);
}
}
This Code gives runtime error called
Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error creating bean with name 'JWTLoginFilter' defined in file [/media/dilanka/Stuff/CODEBASE/Inspection-Application/Inspection-AuthProxy/target/classes/com/shipxpress/inspection/security/jwt/JWTLoginFilter.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: authenticationManager must be specified
The error is as my thought at the beginning, ComponentScan Scanning and initiating JWTLoginFilter. But at that time AuthenticationManager bean has not created. So It is not auto wiring.
So I have to create AuthenticationManager bean before scanning JWTLoginFilter, But It is not possible because it has to create in class that extended by WebSecurityConfigurerAdapter and spring allows one WebSecurityConfigurerAdapter extended class. So I can't initiate it in another class.
Also
#Override
protected void configure(HttpSecurity http) throws Exception {}
has to declare in WebSecurityConfigurerAdapter extended class and this method use jwtLoginFilter. So all
#Autowired
JWTLoginFilter jwtLoginFilter;
and
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
and
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl();
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authProxy").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(jwtLoginFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
has to define it in WebSecurityConfig extends WebSecurityConfigurerAdapter class and has to Control sequence of bean creating and component scanning of the application. Does anyone have an idea? please help me.
updated-->
I Tried to implement JWTLoginFilter as follows,
#Service
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
private TokenAuthenticationService tokenAuthenticationService;
#Autowired
public JWTLoginFilter(AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher("/login"));
}
...
}
But it gives the following error
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| JWTLoginFilter defined in file [/media/dilanka/Stuff/CODEBASE/Inspection-Application/DR-136812421-dbchangesSendAsMail/Inspection-Application/Inspection-AuthProxy/target/classes/com/shipxpress/inspection/security/jwt/JWTLoginFilter.class]
↑ ↓
| webSecurityConfig (field com.shipxpress.inspection.security.jwt.JWTLoginFilter com.shipxpress.inspection.config.WebSecurityConfig.jwtLoginFilter)
└─────┘
I think the problem is, If we auto wire Constructor as above, Then JWTLoginFilter can't create without creating Configuration beans creating. But Configuration beans needs JWTLoginFilter bean. So it can't create without JWTLoginFilter bean.
Thanks.
#Autowired annotation will be processed after constructor of bean has been called. So your exception does not depend on the sequence of bean creating. If you need to invoke setAuthenticationManager from constructor you can apply #Autowired to the constructor:
#Service
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
AuthenticationManager authenticationManager;
private TokenAuthenticationService tokenAuthenticationService;
#Autowired
public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager; //if you will need this instance in future
super(new AntPathRequestMatcher("/login"));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
...
}
Then appropriate bean will be passed to the constructor automatically.
Another solution is to make all initialization in the #PostConstruct method. This method will be called just after #Autowired annotation has been processed:
#Service
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
AuthenticationManager authenticationManager;
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(){
super(new AntPathRequestMatcher("/login"));
}
#PostConstruct
public void postConstruct() {
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
...
}
Spring Boot has multiple conditional annotations to use like #ConditionalOnBean to control the sequencing of bean creation
Look into package org.springframework.boot.autoconfigure.condition for all the conditionals available
For the your example, best way is to have constructor injection of AuthenticationManager in JWTLoginFilter

How do i get a reference to an AuthenticationManager using a Spring java security configuration?

I’m using Spring 4.1.5.RELEASE and Spring Security 3.2.5.RELEASE. I’m doing all security configuration through Java (as opposed to XML). I’m struggling to get a reference to the AuthenticationManager for use with my custom usernamepassword authentication filter …
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages="com.mainco", excludeFilters=#ComponentScan.Filter(Controller.class))
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String ROLE3 = "ROLE3";
private static final String ROLE2 = "ROLE2";
private static final String ROLE1 = "ROLE1";
#Resource(name="userDetailsService")
private UserDetailsService userDetailsService;
#Resource(name="myAuthenticationSuccessHandler")
private MyAuthenticationSuccessHandler authSuccessHandler;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(authenticationFilter(), MyUsernamePasswordAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/403").permitAll()
.antMatchers("/login/**").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/ROLE1/**").hasRole(ROLE1)
.antMatchers("/common/**").hasAnyRole(ROLE1, ROLE2, ROLE3)
.antMatchers("/ROLE3/**").hasAnyRole(ROLE1, ROLE2, ROLE3)
.antMatchers("/ROLE2/**").hasAnyRole(ROLE1, ROLE2)
.antMatchers("/*/**").fullyAuthenticated()
.and().formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authSuccessHandler)
.and().logout().logoutSuccessUrl("/login?logout")
.and().exceptionHandling().accessDeniedPage("/403")
.and().csrf().disable();
}
#Bean(name="passwordEncoder")
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
#Bean
public JSONUsernamePasswordAuthenticationFilter authenticationFilter() {
final MyUsernamePasswordAuthenticationFilter authFilter = new MyUsernamePasswordAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(authSuccessHandler);
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
return authFilter;
}
This configuration fails upon startup with the message
“Caused by: java.lang.IllegalArgumentException: authenticationManager must be specified”.
How do I get a reference to the AuthenticationManager for use with my filter?
You can override authenticationManagerBean() method from WebSecurityConfigurerAdapter to expose AuthenticationManager as a bean like so:
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

Resources