Spring 4.1 and Spring Security 3.2: We implemented a Custom Authentication Provider, that throws a BadCredentialsException if user enters an incorrect password. When the BadCredentialsException is thrown, the ProviderManager.authenticate method is called, which calls the authenticate method in the Custom Authentication again. When a LockedException is thrown, the authenicate method in the Custom Authentication Provider is not called again. We are planning on keeping a count of number of login attempts, so we don't want the authenticate method called twice. Does anyone know why the authenticate method in the custom authentication class would be called twice?
WebConfig:
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Autowired
private AMCiUserDetailsService userDetailsService;
@Autowired
private CustomImpersonateFailureHandler impersonateFailureHandler;
@Autowired
private LoginFailureHandler loginFailureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/jsp/*.css","/jsp/*.js","/images/**").permitAll()
.antMatchers("/login/impersonate*").access("hasRole('ADMIN') or hasRole('ROLE_PREVIOUS_ADMINISTRATOR')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp")
.defaultSuccessUrl("/jsp/Home.jsp",true)
.loginProcessingUrl("/login.jsp")
.failureHandler(loginFailureHandler)
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login.jsp?msg=1")
.permitAll()
.and()
.addFilter(switchUserFilter())
.authenticationProvider(customAuthenticationProvider);
http.exceptionHandling().accessDeniedPage("/jsp/SecurityViolation.jsp"); //if user not authorized to a page, automatically forward them to this page.
http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN));
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//Used for the impersonate functionality
@Bean CustomSwitchUserFilter switchUserFilter() {
CustomSwitchUserFilter filter = new CustomSwitchUserFilter();
filter.setUserDetailsService(userDetailsService);
filter.setTargetUrl("/jsp/Impersonate.jsp?msg=0");
filter.setSwitchUserUrl("/login/impersonate");
filter.setExitUserUrl("/logout/impersonate");
filter.setFailureHandler(impersonateFailureHandler);
return filter;
}
}
Custom Authentication Provider:
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Autowired(required = true)
private HttpServletRequest request;
@Autowired
private AMCiUserDetailsService userService;
@Autowired
private PasswordEncoder encoder;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName().trim();
String password = ((String) authentication.getCredentials()).trim();
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
throw new BadCredentialsException("Login failed! Please try again.");
}
UserDetails user;
try {
user = userService.loadUserByUsername(username);
//log successful attempt
auditLoginBean.setComment("Login Successful");
auditLoginBean.insert();
} catch (Exception e) {
try {
//log unsuccessful attempt
auditLoginBean.setComment("Login Unsuccessful");
auditLoginBean.insert();
} catch (Exception e1) {
// TODO Auto-generated catch block
}
throw new BadCredentialsException("Please enter a valid username and password.");
}
if (!encoder.matches(password, user.getPassword().trim())) {
throw new BadCredentialsException("Please enter a valid username and password.");
}
if (!user.isEnabled()) {
throw new DisabledException("Please enter a valid username and password.");
}
if (!user.isAccountNonLocked()) {
throw new LockedException("Account locked. ");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
List<GrantedAuthority> permlist = new ArrayList<GrantedAuthority>(authorities);
return new UsernamePasswordAuthenticationToken(user, password, permlist);
}
public boolean supports(Class<? extends Object> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
The reason is that you add your authentication provider twice, one time in configure(HttpSecurity)
and one time in configure(AuthenticationManagerBuilder)
. This will create a ProviderManager
with two items, both being your provider.
When authentication is processed, the providers will be asked in order until a success is made, unless a LockedException
or similar status exception is thrown, then the loop will break.
There may be a situtation which you don't override configure(AuthenticationManagerBuilder)
and still same AuthenticationProver
's authenticate
method gets called twice like Phil mentioned in his comment in the accepted answer.
Why is that?
The reason is that when you don't override configure(AuthenticationManagerBuilder)
and have an AuthenticationProvider
bean, it will be registered by Spring Security, you don't have to do anything else.
However, when configure(AuthenticationManagerBuilder)
overridden, Spring Security will invoke it and won't try to register any provider by itself.
If you're curious, you can take a look the related method. disableLocalConfigureAuthenticationBldr
is true if you override configure(AuthenticationManagerBuilder)
.
So, briefly, if you want to register just one custom AuthenticationProvider
then do not override configure(AuthenticationManagerBuilder)
, do not call authenticationProvider(AuthenticationProvider)
in configure(HttpSecurity)
, just make your AuthenticationProviver
implementation bean by annotating @Component
and you're good to go.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With