I am trying to add user ip verification during login process. If ip address of the user is not in the database the application should reject the authentication.
The problem: Given the setup below it turns out that auth.authenticationProvider() is not replacing the default DaoAuthenticationProvider, but adds UserIpAuthenticationProvider as a first AuthenticationProvider in the list.
In the case when username/password combination is incorrect the framework ends up calling UserDetailsService.loadUserByUsername() twice, once from UserIpAuthenticationProvider, another time from internal DaoAuthenticationProvider which throws the final BadCredentialsException().
The question: is there any setting that can be set in Spring Boot so that Spring Security does not add it's own internal instance DaoAuthenticationProvider, but only use my UserIpAuthenticationProvider, which already has all the necessary functionality (perhaps by somehow replacing AuthenticationManagerBuilder to be able to override userDetailsService() method?).
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>(userDetailsService));
}
Configuration: In my understanding, UserDetailsService is supposed to provide all the necessary details about the user so that AuthenticationProvider can make a decision whether the authentication was successful or not.
Since all the necessary information is loaded from the database, it seems natural to extend DaoAuthenticationProvider and add an additional verification in overriden additionalAuthenticationChecks() method (white-listed IP list is in the database, so they are loaded as part of the user object in IpAwareUser).
@Named
@Component
class UserIpAuthenticationProvider extends DaoAuthenticationProvider {
@Inject
public UserIpAuthenticationProvider(UserDetailsService userDetailsService)
{
...
}
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
super.additionalAuthenticationChecks(userDetails, authentication);
WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
IpAwareUser ipAwareUser = (IpAwareUser) userDetails;
if (!ipAwareUser.isAllowedIp(details.getRemoteAddress()))
{
throw new DisabledException("Login restricted from ip: " + details.getRemoteAddress());
}
}
}
This is injected into SecurityConfiguration:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(authenticationFilter);
http.authorizeRequests()
.antMatchers("/", "/javascript/**", "/css/**").permitAll()
.antMatchers("...").access("...")
.anyRequest().authenticated()
.and().formLogin().loginPage("/").permitAll()
.and().logout().invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll()
.and().csrf().disable()
;
}
@Inject
private UserDetailsService userDetailsService;
@Inject
private UserIpAuthenticationProvider userIpAuthenticationProvider;
@Inject
private JsonUsernamePasswordAuthenticationFilter authenticationFilter;
@Bean
public JsonUsernamePasswordAuthenticationFilter authenticationFilter() {
return new JsonUsernamePasswordAuthenticationFilter();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userIpAuthenticationProvider);
auth.userDetailsService(userDetailsService);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() throws Exception {
return new JsonAuthenticationSuccessHandler();
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() throws Exception {
return new JsonAuthenticationFailureHandler();
}
}
and application configuration:
@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackageClasses = {SecurityConfiguration.class, DataController.class, DaoService.class})
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application;
}
}
Any guidance on this will be much appreciated.
The comments on the question contained the answer:
@ArunM: your project gave me an idea: I do not need to call auth.userDetailsService(userDetailsService); in SecurityConfiguration.configure(), which will prevent creation of internal DaoAuthenticationProvider! My UserIpAuthenticationProvider can get instance of UserDetailsService via injection.
The AuthenticationManagerBuilder.userDetailsService
method does not only set the default UserDetailsService
but also applies a DaoAuthenticationConfigurer
which registers the DaoAuthenticationProvider
.
If you want a customized DaoAuthenticationProvider
, pass the UserDetailsService
to the provider in the constructor or inject it. And to prevent the default DaoAuthenticationProvider
from being registered, don't call AuthenticationManagerBuilder.userDetailsService
.
This is also mentioned in this Spring Security issue.
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