In my project, I have two different authentication paths: the first one for normal user login and the second for employer login. So I begin by creating an authentication filter for the user using its AuthenticationProvider, and it works fine. For the second authentication, I create its authentication filter, and when I create another authentication provider for the employer, that is the problem. I get a java.lang.StackOverflowError: null when I try to authenticate the user's username and password using an instance of AuthenticationManager in authenticationmanager.authenticate(userPassAuthToken).
I didn't understand what happened when I created another authentication provider or why the authentication manager threw this exception. I think that the authentication manager is confused about which provider to use.
At first, I create the first authentication for a normal user with its filter and authentication provider as shown in the following code:
- User Filter class
@Component
public class UserPassAuthFilter extends OncePerRequestFilter {
@Autowired
@Lazy
private AuthenticationManager authManager;
@Autowired
private AuthPath authPath;
@Autowired
AuthFilterUtil authFilterUtil;
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
char[] username=request.getHeader("username").toCharArray();
char[] password=request.getHeader("password").toCharArray();
UserPassAuthToken authentication= new UserPassAuthToken(String.valueOf(username),String.valueOf(password));
UserPassAuthToken currentAuth =(UserPassAuthToken)authManager.authenticate(authentication);
if (!currentAuth.isAuthenticated()){
authFilterUtil.onFailureAuth(response,request,null);
}
else {
SecurityContextHolder.getContext().setAuthentication(currentAuth);
if(request.getServletPath().equals(authPath.getUserPasswordFilter_pathShouldDo().get(0))) {
UserDto responseBody = userMapper.mapUserToDto(currentAuth.getUsersDetails().getUsers());
responseBody.setToken(authFilterUtil.prepareTokenToAuthResponse(
currentAuth.getAuthorities(),
currentAuth.getName().toCharArray()
));
authFilterUtil.onSuccessfulAuth(response, responseBody);
}
filterChain.doFilter(request,response);
}
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return !authPath.getUserPasswordFilter_pathShouldDo().contains(request.getServletPath());
}
}
- user Authentication token class
public class UserPassAuthToken extends UsernamePasswordAuthenticationToken {
@Getter
@Setter
private UsersDetails usersDetails;
public UserPassAuthToken(Object principal, Object credentials) {
super(principal, credentials);
}
public UserPassAuthToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
- user authentication provider class
@Component
public class UserPassAuthProvider implements AuthenticationProvider {
@Autowired
UserService userDetailsService;
@Autowired
PasswordEncoder passwordEncoder;
@Override
public UserPassAuthToken authenticate(Authentication authentication) throws AuthenticationException {
String username =authentication.getName();
String password=(String)authentication.getCredentials();
UserPassAuthToken userPassAuthToken;
UsersDetails user=userDetailsService.loadUserByUsername(username);
if (user !=null && password != null && passwordEncoder.matches(password,user.getPassword())){
userPassAuthToken=new UserPassAuthToken(username,user.getPassword(),user.getAuthorities());
userPassAuthToken.setUsersDetails(user);
}else {
userPassAuthToken = new UserPassAuthToken(username, password);
userPassAuthToken.setAuthenticated(false);
}
userPassAuthToken = new UserPassAuthToken(username, password);
userPassAuthToken.setAuthenticated(false);
return userPassAuthToken;
}
@Override
public boolean supports(Class<?> authType) {
return UserPassAuthToken.class.equals(authType);
}
}
- employer authentication provider class
@Component
public class EmployerAuthProvider implements AuthenticationProvider {
@Autowired
EmployerService employerService;
@Autowired
PasswordEncoder passwordEncoder;
@Override
public EmployerPassAuthToken authenticate(Authentication authentication) throws AuthenticationException {
char[]username =authentication.getName().toCharArray();
char[] password=(char[]) authentication.getCredentials();
EmployerPassAuthToken employerPassAuthToken;
UsersDetails employer=employerService.loadUserByUsername(String.valueOf(username));
if (employer !=null && password != null && passwordEncoder.matches(String.valueOf(password),employer.getPassword())){
employerPassAuthToken=new EmployerPassAuthToken(username,employer.getPassword(),employer.getAuthorities());
employerPassAuthToken.setUsersDetails(employer);
}else {
employerPassAuthToken = new EmployerPassAuthToken(username, password);
employerPassAuthToken.setAuthenticated(false);
}
return employerPassAuthToken;
}
@Override
public boolean supports(Class<?> authType) {
return EmployerPassAuthToken.class.equals(authType);
}
}
- security configuration class
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecConfig {
@Autowired
UserPassAuthProvider userPassAuthProvider;
@Autowired
UserPassAuthFilter userPassAuthFilter;
@Autowired
TokenAuthFilter tokenAuthFilter;
@Autowired
AuthPathPrivilege userPrivilege;
@Autowired
AuthPath authPath;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(authPath.getAllNotAuthPath().iterator().next()).permitAll()
.antMatchers(authPath.getAllAuthPath().iterator().next()).authenticated()
.antMatchers(userPrivilege.getSuper_adminPrivilegePath().iterator().next()).hasAnyAuthority("SUPER_ADMIN")
.antMatchers(userPrivilege.getAdminPrivilegePath().iterator().next()).hasAnyAuthority("ADMIN")
.antMatchers(userPrivilege.getManagerPrivilegePath().iterator().next()).hasAnyAuthority("MANAGER")
.antMatchers(userPrivilege.getUserPrivilegePath().iterator().next()).hasAnyAuthority("USER")
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(STATELESS)
.and().authenticationProvider(userPassAuthProvider)
.addFilterAt(userPassAuthFilter , BasicAuthenticationFilter.class)
.addFilterAfter(tokenAuthFilter , BasicAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
**Note: **
- Also using Intellij idea debugging it stuck on the same line of code authManager.authenticate(authentication) in user filter class
- The application didn't enter user authentication provider after creating employer provider.
CodePudding user response:
In addition to Eleftheria's comment:
You are asking Spring Security to provide you an AuthenticationManager
bean by calling authenticationConfiguration.getAuthenticationManager()
.
The
AuthenticationConfiguration
calls theAuthenticationManagerBuilder
to create theAuthenticationManager
, and it only creates theAuthenticationManager
if there is only oneAuthenticationProvider
or only oneUserDetailsService
configured. If it is not able to create it, then a lazy initialization bean will be provided.When the application tries to use the
AuthenticationManager
for the first time, the lazy initialization bean will be triggered, invoking the method that creates the bean, which in your case is:
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
- That method, in turn, invokes
AuthenticationConfiguration#getAuthenticationManager
which will return a lazy initialization bean again since theAuthenticationManager
cannot be created because of <1>, and the cycle continues.
So, why Spring Security didn't initialize the AuthenticationManager
in your case? Because you have 2 AuthenticationProvider
s, and there is no way for Spring Security to know how you want to combine them, that's why Spring Security backs off and lets you deal with the creation of AuthenticationManager
.
How do you fix it?
Since Spring Security cannot create the bean for you, you would have to create the AuthenticationManager
bean yourself. Here is how to do that in your scenario:
@Bean
public AuthenticationManager authenticationManager(List<AuthenticationProvider> myAuthenticationProviders) {
return new ProviderManager(myAuthenticationProviders);
}
For folks that want to use a UserDetailsService
, you can create a DaoAuthenticationProvider
and set the necessary fields.