SpringSecurity重写DaoAuthenticationProvider问题
问题描述
MyDaoAuthenticationProvider重写DaoAuthenticationProvider中additionalAuthenticationChecks方法后,自定义验证密码错误后,抛出BadCredentialsException异常,代码会继续执行,会执行DaoAuthenticationProvider中的additionalAuthenticationChecks发放,导致自定义抛出的BadCredentialsException的异常无用。
public class MyDaoAuthenticationProvider extends DaoAuthenticationProvider {
private static final Logger log = LoggerFactory.getLogger(MyDaoAuthenticationProvider.class);
@SneakyThrows
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!iscLoginJsoupReq(userDetails.getUsername(), presentedPassword)) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
问题发现
在debug中,发现ProviderManager类中authenticate方法中,出现以下代码片段:
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
在getProviders()方法中,会获取到两个AuthenticationProvider,一个是自定义的MyDaoAuthenticationProvider,一个是默认的DaoAuthenticationProvider,在执行DaoAuthenticationProvider中的additionalAuthenticationChecks方法时,就会验证密码成功。
问题解决
在继承WebSecurityConfigurerAdapter的配置类中,在以下方法中:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
ApplicationContext applicationContext = getApplicationContext();
ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
String name = environment.getProperty("spring.profiles.active");
if (ACTIVE.equals(name)) {
auth.authenticationProvider(myProvider());
} else {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
这句代码会生成一个DaoAuthenticationProvider在容器中供使用,且容器中定义了几个Provider,在getProviders()
方法中就会获取到几个,并循环执行。所以在使用自定义的DaoAuthenticationProvider是,需要注释auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
此代码片段。在不使用自定义的Provider时,需放开此代码片段。
遗留问题:重写DaoAuthenticationProvider,抛出的BadCredentialsException为什么会被屏蔽
自己的理解
DaoAuthenticationProvider和MyDaoAuthenticationProvider其实是功能一模一样的两个列,除了重写的additionalAuthenticationChecks()
的实现类不一样,在执行DaoAuthenticationProvider中的additionalAuthenticationChecks时,会得到正确的result进行返回,在代码中,只要得到验证通过,得到正确的result,就会进行返回,代码如下:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed then it will
// publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
// parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
在执行DaoAuthenticationProvider中的result = provider.authenticate(authentication);
时,会得到验证通过的result,在下面代码会返回result:
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
所以不会再代码最后一行,执行抛出throw lastException;
这一行代码。