Spring Security - 记住我的身份验证错误

时间:2014-04-24 14:40:56

标签: spring-mvc spring-security remember-me

我们正在使用Spring MVC,并且遇到与Remember Me身份验证相关的以下问题:

  1. 用户使用"记住我"检查,正常工作,persistent_login表按预期更新
  2. 我们可能会在部署后重新启动应用服务器
  3. 用户刷新页面,我们在图1中看到错误消息,用户被重定向到登录页面(看不到错误)
  4. 尽管有错误,但persistent_login条目令牌已更新(系列保持与刷新前相同),spring Remember Me令牌也保持不变。
  5. 用户第二次刷新页面,他们以任何事情都没有登录
  6. 图1 - 错误消息

    Apr 24, 2014 9:29:15 AM org.apache.catalina.core.StandardWrapperValve invoke
    SEVERE: Servlet.service() for servlet [workmarket] in context with path [] threw exception
    java.lang.ClassCastException: org.springframework.security.web.firewall.FirewalledResponse cannot be cast to org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper
    at org.springframework.security.web.context.HttpSessionSecurityContextRepository.saveContext(HttpSessionSecurityContextRepository.java:99)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:139)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:167)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:182)
    at com.workmarket.web.authentication.CustomLinkedInLoginFilter.doFilter(CustomLinkedInLoginFilter.java:100)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at com.workmarket.web.authentication.CustomLoginFilter.doFilter(CustomLoginFilter.java:100)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.saml.metadata.MetadataGeneratorFilter.doFilter(MetadataGeneratorFilter.java:86)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:323)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:173)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)
    

    图2 - 安全过滤器

    <sec:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <sec:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
    <sec:custom-filter ref="customLoginFilter" position="FORM_LOGIN_FILTER"/>
    <sec:custom-filter ref="customLinkedInLoginFilter" after="FORM_LOGIN_FILTER"/>
    <sec:custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER"/>
    <sec:custom-filter ref="switchUserProcessingFilter" position="SWITCH_USER_FILTER"/>
    <sec:custom-filter ref="authenticatedUserInitializer" before="FILTER_SECURITY_INTERCEPTOR"/>
    <sec:custom-filter ref="publicWorkRequestFilter" after="FILTER_SECURITY_INTERCEPTOR"/>
    <sec:custom-filter ref="securityContextCleanupFilter" after="SESSION_MANAGEMENT_FILTER"/>
    <sec:custom-filter ref="customLogoutFilter" position="LOGOUT_FILTER"/>
    

    图3 - 记住我的设置

    <!-- Remember me -->
    <bean id="rememberMeFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
        <constructor-arg index="0" ref="org.springframework.security.authenticationManager"/>
        <constructor-arg index="1" ref="rememberMeServices"/>
    </bean>
    
    <bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
        <constructor-arg index="0" value="[REMOVED]"/>
    </bean>
    
    <bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
        <constructor-arg index="0" value="[REMOVED]"/>
        <constructor-arg index="1" ref="userDetailsService"/>
        <constructor-arg index="2" ref="persistentTokenRepository"/>
    </bean>
    

    版本

    1. spring framework - 3.2.4.RELEASE
    2. spring security - 3.1.0.RELEASE
    3. spring-security-saml2-core - 1.0.0.RC2
    4. opensaml - 2.5.3
    5. ======更新======

      我们发现删除这两个与SAML相关的过滤器可以解决这个问题,但我们确实需要这些过滤器才能工作......

      <sec:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
      <sec:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
      

      ======更新2 ======

      samlFilter定义的详细信息。

      <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/SSO/**" filters="samlWebSSOProcessingFilter"/>
        </security:filter-chain-map>
      </bean>
      
      <bean id="samlEntryPoint" class="org.springframework.security.saml.SAMLEntryPoint">
        <property name="defaultProfileOptions">
          <bean class="org.springframework.security.saml.websso.WebSSOProfileOptions">
            <property name="includeScoping" value="false"/>
          </bean>
        </property>
      </bean>
      
      <bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
        <property name="authenticationManager" ref="samlAuthenticationManager"/>
        <property name="authenticationSuccessHandler">
          <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
            <property name="defaultTargetUrl" value="/"/>
          </bean>
        </property>
      </bean>
      

      提前致谢。

2 个答案:

答案 0 :(得分:8)

这是由SecurityContextPersistenceFilter之后使用的FilterChainProxy引起的。具体来说,FilterChainProxy的HttpFirewall正在用一个不再实现SavedRequest的DefaultHttpFirewall替换HttpServletResponse。为了解决这个问题,你可以在samlFilter FilterChainProxy中注入一个自定义的HttpFirewall,它返回传递给它的相同的HttpServletResponse。例如:

public class DoNothingHttpFirewall implements HttpFirewall {

    public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
        return new MyFirewalledRequest(request);
    }

    public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
        return response;
    }

    private static class MyFirewalledRequest extends FirewalledRequest {
         MyFirewalledRequest(HttpServletRequest r) {
             super(r);
         }
         public void reset() {}
    }
}

然后您可以使用以下方式连接它:

<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/SSO/**" filters="samlWebSSOProcessingFilter"/>
  </security:filter-chain-map>
  <property name="firewall">
    <bean class="DoNothingHttpFirewall"/>
  </property>
</bean>

我已经记录了一张票,以便将来透明地使用https://jira.spring.io/browse/SEC-2578

答案 1 :(得分:0)

我也遇到了这个问题。我的解决方案的灵感来自@ RobWinch的答案,但使用的是更安全的实现:

  1. 创建一个扩展DefaultHttpFirewall
  2. 的类
  3. 覆盖getFirewalledResponse(HttpResponse response)方法,替换为检查响应类型的方法;如果是instanceof SaveContextOnUpdateOrErrorResponseWrapper,则平均返回提供的响应,否则返回return super.getFirewalledResponse()
  4. 使用@ RobWinch的回答中列出的属性注入来注入此类的bean。
  5. 此实现与FilterChainProxy和DefaultHttpFirewall的先前行为更加一致,因为当类型匹配容易出错的响应类型时,它只会轻易返回传入的响应。否则,调用super方法,保留父节点的逻辑。此外,保留了getFirewalledRequest(...)方法的逻辑,因为在这种情况下,这似乎不是错误的来源。