I'm learning about security config in a tutorial and will go to the JWT Authentication material. I have a problem with circular dependencies error in AuthenticationManager. I've checked everything but I can't see what I'm doing wrong. I hope you guys can help me with the problem I'm having.
..>bookcatalog>config>SecurityConfig.class
package com.bluedev.bookcatalog.config;
import com.bluedev.bookcatalog.security.filter.UsernamePasswordAuthenticationFilter;
import com.bluedev.bookcatalog.security.provider.UsernamePasswordAuthProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String V1_URL = "/v1/**";
private static final String V2_URL = "/v2/**";
@Autowired
private AuthenticationSuccessHandler successHandler;
@Autowired
private AuthenticationFailureHandler failureHandler;
@Autowired
private UsernamePasswordAuthProvider usernamePasswordAuthProvider;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private ObjectMapper objectMapper;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(appUserService).passwordEncoder(appConfig.passwordEncoder());
auth.authenticationProvider(usernamePasswordAuthProvider);
}
protected UsernamePasswordAuthenticationFilter buildUsernamePasswordAuthFilter(String loginEntryPoint){
UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter(loginEntryPoint, successHandler, failureHandler, objectMapper);
filter.setAuthenticationManager(authenticationManager);
return filter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(V1_URL, V2_URL).authenticated()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(buildUsernamePasswordAuthFilter("/v1/login"), org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.class);
}
}
..>bookcatalog>security>filter>UsernamePasswordAuthenticationFilter.java
package com.bluedev.bookcatalog.security.filter;
import com.bluedev.bookcatalog.dto.LoginRequestDTO;
import com.bluedev.bookcatalog.exception.BadRequestException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private final AuthenticationSuccessHandler successHandler;
private final AuthenticationFailureHandler failureHandler;
private final ObjectMapper objectMapper;
public UsernamePasswordAuthenticationFilter(String defaultFilterProcessesUrl, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler, ObjectMapper objectMapper) {
super(defaultFilterProcessesUrl);
this.successHandler = successHandler;
this.failureHandler = failureHandler;
this.objectMapper = objectMapper;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
LoginRequestDTO dto = objectMapper.readValue(request.getReader(), LoginRequestDTO.class);
if(StringUtils.isBlank(dto.getUsername()) || StringUtils.isBlank(dto.getPassword())){
throw new BadRequestException("username & password must be provided.");
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword());
return this.getAuthenticationManager().authenticate(token);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
}
..>bookcatalog>security>provider>UsernamePasswordAuthProvider.java
package com.bluedev.bookcatalog.security.provider;
import com.bluedev.bookcatalog.service.AppUserService;
import lombok.AllArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@AllArgsConstructor
@Component
public class UsernamePasswordAuthProvider implements AuthenticationProvider {
private final AppUserService appUserService;
private final PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
UserDetails appUser = appUserService.loadUserByUsername(username);
if(!passwordEncoder.matches(password, appUser.getPassword())){
throw new BadCredentialsException("invalid.username.password");
}
return new UsernamePasswordAuthenticationToken(appUser, null, appUser.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
..>bookcatalog/security/handler/UsernamePasswordAuthSuccessHandler.java
package com.bluedev.bookcatalog.security.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
@Component
public class UsernamePasswordAuthSuccessHandler implements AuthenticationSuccessHandler {
private final ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Map<String, String> resultMap = new HashMap<>();
resultMap.put("result", "SUCCESS");
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), resultMap);
}
}
..>bookcatalog/security/handler/UsernamePasswordAuthFailureHandler.java
package com.bluedev.bookcatalog.security.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
@Component
public class UsernamePasswordAuthFailureHandler implements AuthenticationFailureHandler {
private final ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
Map<String, String> resultMap = new HashMap<>();
resultMap.put("result", "FAILURE");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
objectMapper.writeValue(response.getWriter(), resultMap);
}
}
Error Result
2022-06-17 18:31:06.226 ERROR 18092 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌──->──┐
| securityConfig (field private org.springframework.security.authentication.AuthenticationManager com.bluedev.bookcatalog.config.SecurityConfig.authenticationManager)
└──<-──┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
Process finished with exit code 1
CodePudding user response:
Remove below line from SecurityConfig
@Autowired
private AuthenticationManager authenticationManager;
And modify the buildUsernamePasswordAuthFilter
function to use authenticationManagerBean()
function
protected UsernamePasswordAuthenticationFilter buildUsernamePasswordAuthFilter(String loginEntryPoint){
UsernamePasswordAuthenticationFilter filter = new UsernamePasswordAuthenticationFilter(loginEntryPoint, successHandler, failureHandler, objectMapper);
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}