多个身份验证提供程序:如果身份验证失败,请不要委派

hkmswyz6  于 2021-09-30  发布在  Java
关注(0)|答案(1)|浏览(477)

我正在尝试在spring身份验证服务器(spring security<5.0)中配置多个身份验证提供程序(主要和次要)。我知道用户将在主提供商或第二提供商上找到,而不会同时在这两个提供商上找到。因此,如果主提供者的身份验证失败,我想给出一个正确的消息。
根据“认证”方法文档:
返回:包含凭据的完全经过身份验证的对象。如果authenticationprovider无法支持对传递的身份验证对象的身份验证,则可能返回null。在这种情况下,将尝试下一个支持提供的身份验证类的authenticationprovider。
抛出:authenticationexception-如果身份验证失败。
基于此,我在主提供程序上实现了authenticate方法,如下所示(我将省略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,但服务器仍将委托给辅助提供程序,并且最终消息将为“user not found”,这会产生误导。
我认为badcredentialsexception将完成身份验证链并向客户机/用户报告,但事实似乎并非如此。
我错过什么了吗?

xj3cbfub

xj3cbfub1#

好吧,我想起来了。
提供程序管理器是在我的服务器上使用的默认身份验证管理器。如果发生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的一个较小版本。此选项不会尝试捕获提供商启动的异常:

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()
        );
    }
}

然后,在WebSecurity配置中:

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

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

相关问题