Spring Security 和嵌套 FilterChainProxy 编写 SAML Service Provider

Posted

技术标签:

【中文标题】Spring Security 和嵌套 FilterChainProxy 编写 SAML Service Provider【英文标题】:Spring Security and nested FilterChainProxy writing SAML Service Provider 【发布时间】:2013-06-11 16:49:45 【问题描述】:

我正在尝试找出涉及 Spring Security 和 SAML 的问题。我们正在尝试使用 Spring Security (spring-security-core-3.1.1.RELEASE.jar) 和 SAML (spring-security-saml2-core-1.0.0-RC1-SNAPSHOT.jar) 将我们的产品修改为SAML SP。编辑:这是我的安全相关上下文 xml 的(我认为!)相关部分。如您所见,它几乎与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>

症状是,在通过 IDP 进行身份验证后,我的 SP 页面立即正确显示;但是,更改 URL(例如单击任何链接)会使我立即返回 IDP。我想我已经找到了原因,但我不知道为什么并非总是如此。

我对 Spring Security 的理解是,授权检查都是基于 SecurityContextHolder 的。即,在持有者上放置一个 Authentication 对象,然后一切都围绕它进行身份验证检查。然后 SecurityContextPersistenceFilter 负责维护会话存储库以匹配。

所以,当我跟踪 Spring Security 代码时,我看到 SecurityContextPersistenceFilter 的代码如下:

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

到目前为止,一切都很好。然后,查看FilterChainProxy,发现:

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
    

这似乎还可以。由于 FilterChainProxy 应该只被调用一次,在所有 Spring Security 过滤器的基础上,此时清除 SecurityContextHolder 不是问题。

但是,这不是正在发生的事情。实际发生的是,在 SecurityContextPersistenceFilter 有机会将其从上下文中读取到 contextAfterChainExecution 之前,FilterChainProxy 中的 clearContext 被调用。发生这种情况的原因是因为 FilterChainProxy 实际上在调用链中出现了两次。我知道这一点是因为我在 FilterChainProxy.doFilter 中设置了一个断点,并且它被调用了两次。第一次调用时,它的 FilterChain 中有另一个 FilterChainProxy 实例。这是FilterChainProxy的getFilters方法返回的过滤器堆栈:

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

有了这个过滤器链,我不明白 SecurityContextPersistenceFilter 是如何工作的:似乎 SecurityContextHolder 总是在有机会持久化之前被清除。

这里有什么明显的错误吗?我是否误解了 Spring Security 中的某些内容(很可能!)

【问题讨论】:

【参考方案1】:

我无法得到任何明确的声明,但问题似乎在于 Spring Security 3.1.1 不能很好地与 Spring SAML 或任何使用相同类型的嵌套 FilterChainProxys 的实现配合使用。看来 FilterChainProxy 已为 3.1.1 完全重写。当我查看最新版本 (3.1.4) 时,我注意到 finally 子句中有一个检查,仅当它是第一次调用过滤器时才清除 SecurityContextHolder ("SEC-1950")。

因此,将 spring security 升级到 3.1.4 解决了这个问题。

【讨论】:

感谢您发布解决方案。我也遇到了同样的问题,您的解决方案也对我有用。 很高兴听到我不是唯一一个看到它的人。感谢您的跟进! 即使在将 spring 安全版本升级到 3.1.4.RELEASE 之后,我也面临同样的问题。

以上是关于Spring Security 和嵌套 FilterChainProxy 编写 SAML Service Provider的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security:提取 oidc 角色声明给 Spring 当局

社交登录,spring-security-oauth2 和 spring-security-jwt?

Spring Security入门(3-4)Spring Security 异常处理异常传递和异常获取

Spring 框架 4.0 和 Spring security 3.2.4 上的 Spring Security SAML 扩展

org.springframework.security.oauth 和 org.codehaus.spring-security-oauth 有啥区别?

Spring Security入门(3-5)Spring Security 的鉴权 - 决策管理器和投票器