Summary

When you use multiple sec:authentication-managers, you can't @Autowire the AuthenticationEventPublisher because every sec:authentication-manager will instantiate its own public bean

Detailed explanation

I work on a plugin-based authentication architecture. All plugins must be switched by Spring profiles and should coexist if possible (all active have no effect).

Currently I have two kinds of plugins:

  • SSO plugins have their own filter and a dedicated authentication manager, e.g.
  • For username-password-based plugins (e.g. Active Directory, database, custom lookup) I had to invent an ExtensibleAuthenticationManager that basically wraps a ProviderManager and scans for AuthenticationProviders that support UsernamePasswordToken
public class ExtensibleAuthenticationManager implements AuthenticationManager, InitializingBean
{
    private final static Logger log = LogManager.getLogger();

    private AuthenticationManager parentAuthenticationManager;
    private ProviderManager providerManager;
    private Class<? extends Authentication> limitToClass;

    @Autowired
    private ListableBeanFactory beanFactory;
    @Autowired
    private MessageSource messageSource;
    @Autowired(required = false)
    private AuthenticationEventPublisher authenticationEventPublisher;

Actual Behavior

Reference

Since Spring Security creates a globally-visible bean of type DefaultAuthenticationEventPublisher every time it creates an authentication manager, you can autowire it

Expected Behavior

There should be either one globally-visible bean of type AuthenticationEventPublisher that can be autowired in other beans if required, or the definition of a new AuthenticationManager should inject an instance of DefaultAuthenticationEventPublisher that is private to the authentication manager.

I would prefer the latter, because if one writes their own security plugin there will be actually no public AuthenticationEventPublisher to Autowire, and developer is forced to find their own way (i.e. declare explicitly) to autowire into their authenticatio components

Configuration

<beans profile="CEDACRISEC" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sec="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                           http://www.springframework.org/schema/security  http://www.springframework.org/schema/security/spring-security-4.2.xsd"
>
    <sec:authentication-manager id="cedacriAuthenticationManager">
        <sec:authentication-provider ref="cedacriAuthenticationProvider"/>
    </sec:authentication-manager>

    <bean id="formLoginFilter_cedacri" class="it.phoenix.web.security.cedacri.CedacriAuthenticationFilter">


---------------------------


<beans profile="ICCREASEC"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-4.2.xsd">

    <bean id="preAuthFilter_iccrea" class="it.phoenix.web.security.spring.PreAuthenticatedIccreaProcessingFilter">
        <property name="sessionAuthenticationStrategy"
                  ref="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#0"/>
        <property name="authenticationManager" ref="iccreaAuthenticationManager"/>
        <property name="authenticationSuccessHandler" ref="authenticationHandler"/>
        <property name="authenticationFailureHandler" ref="authenticationHandler"/>
    </bean>

    <sec:authentication-manager id="iccreaAuthenticationManager">
        <sec:authentication-provider ref="iccreaPreAuthProvider"/>
    </sec:authentication-manager>

---------------------------

<beans profile="PREAUTHSEC" xmlns:sec="http://www.springframework.org/schema/security" xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                        http://www.springframework.org/schema/security
                        http://www.springframework.org/schema/security/spring-security-4.2.xsd"
>

    <bean id="preAuthFilter_preauthsec" class="it.phoenix.web.security.spring.PreAuthenticatedProcessingFilter">
        <property name="sessionAuthenticationStrategy"
                  ref="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#0"/>
        <property name="authenticationFailureHandler" ref="authenticationHandler" />
        <property name="authenticationSuccessHandler" ref="authenticationHandler" />
        <property name="authenticationManager" ref="preauthAuthenticationManager"/>
        <property name="principalRequestHeader" value="${httpheader.preauth.user:SM_USER}"/>
        <property name="rolesRequestHeader" value="${httpheader.preauth.role:SM_ROLES}"/>
        <property name="rolesDelimiter" value="${httpheader.preauth.rolesDelimiter:,}"/>
    </bean>

    <sec:authentication-manager id="preauthAuthenticationManager">
        <sec:authentication-provider ref="profilesAuthenticationProvider"/>
    </sec:authentication-manager>

</beans>

---------------------------

<sec:http name="httpSecurityContext" auto-config="false" use-expressions="true" access-decision-manager-ref="accessDecisionManager"
              authentication-manager-ref="usernamePasswordAuthenticationManager"
              entry-point-ref="authenticationEntryPoint"
    >
   .............

    <bean id="usernamePasswordAuthenticationManager" class="it.phoenix.web.security.spring.ExtensibleAuthenticationManager" lazy-init="true">
        <property name="limitToClass" value="org.springframework.security.authentication.UsernamePasswordAuthenticationToken" />
    </bean>
</sec:http>


Version

Using version 4.2.11 but should affect all 4.2.x

Related

4400

4269

Comment From: djechelon

Anyway, I implemented a workaround by replacing Autowired with XML property injection of a customized event publisher defined as protected bean

    <bean id="usernamePasswordAuthenticationManager" class="it.phoenix.web.security.spring.ExtensibleAuthenticationManager" lazy-init="true">
        <property name="limitToClass" value="org.springframework.security.authentication.UsernamePasswordAuthenticationToken"/>
        <property name="authenticationEventPublisher">
            <bean id="defaultAuthenticationEventPublisher" class="org.springframework.security.authentication.DefaultAuthenticationEventPublisher"/>
        </property>
    </bean>

Comment From: rwinch

Thanks for the report @djechelon! Would you be willing to submit a PR to fix this?

Comment From: djechelon

@djechelon created a pull request right now. It is an early proposal, without tests (yet)

Comment From: jzheaux

Note that this is the related PR.