多个身份验证提供者:如果身份验证失败,请不要委托
Posted
技术标签:
【中文标题】多个身份验证提供者:如果身份验证失败,请不要委托【英文标题】:Multiple Authentication Providers: do not delegate if authentication fails 【发布时间】:2021-08-24 18:40:32 【问题描述】:我正在尝试在 Spring 身份验证服务器(Spring Security
根据“认证”方法documentation:
返回:一个完全经过身份验证的对象,包括凭据。可能 如果 AuthenticationProvider 无法支持,则返回 null 传递的 Authentication 对象的身份验证。在这种情况下, 下一个 AuthenticationProvider 支持所呈现的 将尝试身份验证类。
抛出:AuthenticationException - 如果身份验证失败。
基于此,我在主提供者上实现了如下身份验证方法(我将省略 SecondaryAuthProvider 实现):
//PrimaryAuthProvider.class
public Authentication authenticate(Authentication authentication)
var user = authServices.getLdapUser(authentication.getName());
//log and let the next provider handle it
if (user == null)
logServices.userNotFound(new LogServices.AuthFailure(authentication.getName()));
return null;
if (passwordMatches(authentication.getCredentials(), user.getStringPassword()))
return authenticatedToken(user);
else
logServices.authFailure(new LogServices.AuthFailure(authentication.getName()));
throw new BadCredentialsException("Invalid password");
在 WebSecurity 中,我还注入了我的提供程序:
protected void configure(AuthenticationManagerBuilder auth)
auth.authenticationProvider(primaryAuthProvider);
auth.authenticationProvider(secondaryAuthProvider);
如果满足以下条件,这将正确处理:
用户告知正确的登录名/密码,无论提供商如何。 无论密码是否正确,都无法在主要提供商上找到用户。如果在主要提供者上找到用户并且他的密码错误,则会抛出 BadCredentialsException,但服务器仍将委托给辅助提供者,最终消息将是“找不到用户”,这具有误导性。
我认为 BadCredentialsException 会完成身份验证链并报告给客户端/用户,但事实并非如此。
我错过了什么吗?
【问题讨论】:
【参考方案1】:好的,刚刚想通了。
Provider Manager 是我服务器上使用的默认身份验证管理器。如果发生 AuthenticationException,它的身份验证方法确实会委托给下一个提供者:
for (AuthenticationProvider provider : getProviders())
if (!provider.supports(toTest))
continue;
//(...)
try
result = provider.authenticate(authentication);
if (result != null)
copyDetails(authentication, result);
break;
catch (AccountStatusException | InternalAuthenticationServiceException e)
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
catch (AuthenticationException e)
lastException = e;
我找到了两种方法。
第一个:如果身份验证失败,则在主提供者上提供未经身份验证的令牌并且不抛出任何异常:
//PrimaryAuthProvider.class
public Authentication authenticate(Authentication authentication) throws AuthenticationException
var user = authServices.getLdapUser(authentication.getName());
if (user == null) return null;
if (passwordMatches(authentication.getCredentials(), user.getStringPassword()))
return authenticatedToken(user);
else
return unauthenticatedToken(user);
private UsernamePasswordAuthenticationToken unauthenticatedToken(LdapUser user)
//Using 2 parameter constructor => authenticated = false
return new UsernamePasswordAuthenticationToken(
user.getLogin(),
user.getStringPassword(),
);
这样做的缺点是会以英文显示默认消息。我需要在其他地方拦截异常并用葡萄牙语抛出一个新异常。
第二个(希望我使用过):将我自己的 AuthorizationManager 实现为 ProviderManager 的较小版本。这个不会尝试捕获 Providers 发起的异常:
public class CustomProviderManager implements AuthenticationManager
private final List<AuthenticationProvider> providers;
public CustomProviderManager(AuthenticationProvider... providers)
this.providers = List.of(providers);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
for (var provider : providers)
if (!provider.supports(authentication.getClass())) continue;
//let exceptions go through
var result = provider.authenticate(authentication);
if (result != null)
return result;
throw new ProviderNotFoundException(
"No provider for " + authentication.getName()
);
然后,在 WebSecurityConfig:
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return authenticationManager();
@Override
protected AuthenticationManager authenticationManager()
return new CustomProviderManager(primaryAuthProvider, secondaryAuthProvider);
// Don't need it anymore
// @Override
// protected void configure(AuthenticationManagerBuilder auth)
// auth.authenticationProvider(authenticationProvider);
// auth.authenticationProvider(secondaryAuthProvider);
//
第二个需要更多的编码,但给了我更多的控制权。
【讨论】:
以上是关于多个身份验证提供者:如果身份验证失败,请不要委托的主要内容,如果未能解决你的问题,请参考以下文章