With Spring Security 5.7.4, the framework has depercated WebSecurityConfigurerAdapter, requiring develoeprs to adopt new configuration patterns. When implementing custom authentication filters that extend UsernamePasswordAuthenticationFilter and override the attemptAuthentication method, obtaining an AuthenticationManager instance becomes challenging without the traditional adapter approach.
Deprecated Configuration Pattern
The legacy approach involved extending WebSecurityConfigurerAdapter and overriding the authenticationManagerBean() method:
public class CustomAuthFilter extends UsernamePasswordAuthenticationFilter {
public CustomAuthFilter(AuthenticationManager manager) {
super(manager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
if (!req.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Unsupported authentication method: " + req.getMethod());
}
HttpSession session = req.getSession();
String storedCode = (String) session.getAttribute("captcha_value");
if (isJsonRequest(req)) {
Map<string string=""> credentials = extractCredentialsFromJson(req);
validateCaptcha(storedCode, credentials.get("captcha"));
String user = credentials.get(getUsernameParameter());
String pass = credentials.get(getPasswordParameter());
if (isEmpty(user)) {
throw new AuthenticationServiceException("Username cannot be empty");
}
if (isEmpty(pass)) {
throw new AuthenticationServiceException("Password cannot be empty");
}
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(user, pass);
setDetails(req, token);
return getAuthenticationManager().authenticate(token);
} else {
validateCaptcha(storedCode, req.getParameter("captcha"));
return super.attemptAuthentication(req, res);
}
}
private boolean isJsonRequest(HttpServletRequest request) {
String contentType = request.getContentType();
return MediaType.APPLICATION_JSON_VALUE.equals(contentType) ||
MediaType.APPLICATION_JSON_UTF8_VALUE.equals(contentType);
}
private Map<string string=""> extractCredentialsFromJson(HttpServletRequest request) {
try {
return new ObjectMapper().readValue(request.getInputStream(), Map.class);
} catch (IOException e) {
return new HashMap<>();
}
}
private void validateCaptcha(String expected, String actual) {
if (isEmpty(actual)) {
throw new AuthenticationServiceException("Captcha cannot be empty!");
}
if (isEmpty(expected)) {
throw new AuthenticationServiceException("Please refresh captcha!");
}
if (!expected.equalsIgnoreCase(actual)) {
throw new AuthenticationServiceException("Invalid captcha!");
}
}
private boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
}
</string></string>
Modern Configuration Approaches
Two primary methods exist for configuring AuthenticationManager without the deprecated adapter:
Global AuthenticationManager Configuration
Create a bean that provides the authentication manager through AuthenticationConfiguration:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/captcha", "/auth/login").permitAll()
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(new CustomAuthEntryPoint())
)
.addFilterBefore(customAuthFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
@Bean
public CustomAuthFilter customAuthFilter() throws Exception {
CustomAuthFilter filter = new CustomAuthFilter(authenticationManager(
new org.springframework.security.config.annotation.configuration.AuthenticationConfiguration()));
filter.setFilterProcessesUrl("/auth/login");
filter.setUsernameParameter("username");
filter.setPasswordParameter("password");
filter.setAuthenticationSuccessHandler(new CustomAuthSuccessHandler());
filter.setAuthenticationFailureHandler(new CustomAuthFailureHandler());
return filter;
}
}
Custom DSL Configuration Approach
Create a custom DSL class that extends AbstractHttpConfigurer:
public class AuthenticationFilterDsl extends AbstractHttpConfigurer<authenticationfilterdsl httpsecurity=""> {
@Override
public void init(HttpSecurity builder) throws Exception {
// Register authentication manager dependency
AuthenticationManager manager = builder.getSharedObject(AuthenticationManager.class);
CustomAuthFilter filter = createAuthFilter(manager);
builder.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class);
}
private CustomAuthFilter createAuthFilter(AuthenticationManager manager) throws Exception {
CustomAuthFilter filter = new CustomAuthFilter(manager);
filter.setFilterProcessesUrl("/auth/login");
filter.setUsernameParameter("username");
filter.setPasswordParameter("password");
filter.setAuthenticationSuccessHandler(new CustomAuthSuccessHandler());
filter.setAuthenticationFailureHandler(new CustomAuthFailureHandler());
return filter;
}
public static AuthenticationFilterDsl create() {
return new AuthenticationFilterDsl();
}
}
</authenticationfilterdsl>
Apply the custom DSL in your security configuration:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/captcha", "/auth/login").permitAll()
.anyRequest().authenticated()
)
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.apply(AuthenticationFilterDsl.create());
return http.build();
}