多个身份验证提供者:如果身份验证失败,请不要委托

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);
//    

第二个需要更多的编码,但给了我更多的控制权。

【讨论】:

以上是关于多个身份验证提供者:如果身份验证失败,请不要委托的主要内容,如果未能解决你的问题,请参考以下文章

创建委托身份验证提供程序(Spring Security)

Sonarqube 委托对 Gitlab 的身份验证失败

facebook怎么验证?

Asp.net 委托

数据库本地连接失败,错误代码18456怎么解决?

在 Lumen 中创建多个身份验证提供程序