Question

I am trying to figure out a problem involving Spring Security and SAML. We are trying to use Spring Security (spring-security-core-3.1.1.RELEASE.jar) and SAML (spring-security-saml2-core-1.0.0-RC1-SNAPSHOT.jar) to modify our product to be a SAML SP. EDIT: Here is the (I think!) relevant section of my security-related context xml. As you can see, it's almost identical to this sample XML.

<!-- Entry point to initialize authentication, default values taken from properties file -->
<bean id="samlEntryPoint" class="com.myproduct.samlsp.impl.PSSAMLEntryPoint">
    <property name="defaultProfileOptions">
        <bean class="org.springframework.security.saml.websso.WebSSOProfileOptions">
            <property name="includeScoping" value="false"/>
        </bean>
    </property>
</bean>

<!-- Unsecured pages -->
<security:http security="none" pattern="/saml/web/**"/>
<security:http security="none" pattern="/logout.jsp"/>
<security:http security="none" pattern="/favicon.ico"/>
<security:http security="none" pattern="/images/**"/>
<security:http security="none" pattern="/scripts/**"/>
<security:http security="none" pattern="/flash/**"/>
<security:http security="none" pattern="/loggedout.html"/>

<!-- Secured pages -->
<security:http entry-point-ref="samlEntryPoint">
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</security:http>

<!-- IDP Discovery Service -->
<bean id="samlIDPDiscovery" class="org.springframework.security.saml.SAMLDiscovery">
    <property name="idpSelectionPath" value="/WEB-INF/security/idpSelection.jsp"/>
</bean>

<bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
    <security:filter-chain-map request-matcher="ant">
        <security:filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
        <security:filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
        <security:filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
        <security:filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
        <security:filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
        <security:filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
        <security:filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
    </security:filter-chain-map>
</bean>

The symptom is that, immediately after authentication with the IDP, my SP's page shows up correctly; however, changing the URL (e.g. clicking any link) sends me immediately back to the IDP. I think I've figured out why, but I don't know why this is not always the case.

My understanding of Spring Security is that authorization checking is all based around the SecurityContextHolder. Namely, put an Authentication object on the holder, and everything does auth checks around it. Then SecurityContextPersistenceFilter is responsible for maintaining the session repository to match.

So, as I trace through the Spring Security code, I see SecurityContextPersistenceFilter with the following code:

SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
  SecurityContextHolder.setContext(contextBeforeChainExecution);
  chain.doFilter(holder.getRequest(), holder.getResponse());
} finally {
  SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
  // Crucial removal of SecurityContextHolder contents - do this before anything else.
  SecurityContextHolder.clearContext();
  repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
  ....
}

So far, so good. Then, I look at FilterChainProxy and find:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    try {
        doFilterInternal(request, response, chain);
    } finally {
        // SEC-1950
        SecurityContextHolder.clearContext();  <------- Key line here
    }
}

This still seems ok. Since FilterChainProxy should only be invoked once, at the base of all the Spring Security filters, it's not a problem to clear the SecurityContextHolder at that point.

HOWEVER, this is not what's happening. What's actually happening is the clearContext in FilterChainProxy is called BEFORE SecurityContextPersistenceFilter gets a chance to read it out of the context into contextAfterChainExecution. The reason this is happening is because FilterChainProxy actually occurs twice in the call chain. I know this because I set a breakpoint in FilterChainProxy.doFilter, and it IS called twice. The first time it's called, it has another instance of FilterChainProxy in its FilterChain. Here is the stack of Filters returned by FilterChainProxy's getFilters method:

org.springframework.security.saml.metadata.MetadataGeneratorFilter@78104d3c
org.springframework.security.web.context.SecurityContextPersistenceFilter@168c795e
FilterChainProxy[ Filter Chains: [ .... my patterns ] ],
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7fffde92
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@e2d09d7
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1c2b968f
org.springframework.security.web.session.SessionManagementFilter@395f222a
org.springframework.security.web.access.ExceptionTranslationFilter@372e6f09
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7dab91aa

With this chain of filters, I don't understand how SecurityContextPersistenceFilter can ever work: it seems like the SecurityContextHolder will always be cleared before it gets a chance to persist it.

Is there something obvious wrong here? Am I misunderstanding something in Spring Security (very possible!)

Was it helpful?

Solution

I was not able to get any definitive statement of this, but the problem appears to be with Spring Security 3.1.1 not playing well with Spring SAML or indeed any implementation that uses the same kind of nested FilterChainProxys. It appears FilterChainProxy was completely rewritten for 3.1.1. When I looked at the latest release (3.1.4), I noticed that there is a check in the finally clause that only clears the SecurityContextHolder ("SEC-1950") IF it is the first invocation of the filter.

Thus, upgrading spring security to 3.1.4 solved the problem.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top