【问题标题】:Spring Security - Remember Me Authentication ErrorSpring Security - 记住我的身份验证错误
【发布时间】:2014-06-09 22:20:09
【问题描述】:

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

  1. 选中“记住我”的用户登录,工作正常,persistent_login 表按预期更新
  2. 我们重新启动应用服务器,可能是在部署等之后
  3. 用户刷新页面,我们看到图1中的错误消息,用户被重定向到登录页面(没有看到错误)
  4. 尽管有错误,但persistent_login 条目令牌已更新(系列与刷新前保持一致),spring 记住我令牌也保持不变。
  5. 用户第二次刷新页面,他们就像什么都没发生一样登录

图 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 框架 -- 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

====== 更新 ======

我们发现删除这两个与 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>

提前致谢。

【问题讨论】:

  • 您可以尝试使用 Spring Security 3.1.6.RELEASE 吗?如果这不起作用,您可以尝试 3.1.2.RELEASE,它与示例应用程序使用的版本相同。
  • 两个版本都试过了,还是遇到这个问题。这一定与 SAML 过滤器有关。
  • 您是否尝试仅删除两个过滤器之一并查看是否会产生错误?您能否为导致问题的两个过滤器提供 bean 定义。
  • 绝对是 samlFilter,删除它,一切都会恢复正常。我将在上面添加 samlFilter bean 定义。

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


【解决方案1】:

这是由在 SecurityContextPersistenceFilter 之后使用的 FilterChainProxy 引起的。具体来说,FilterChainProxy 的 HttpFirewall 将 HttpServletResponse 替换为不再实现 SavedRequest 的 DefaultHttpFirewall。为了解决这个问题,您可以将自定义 HttpFirewall 注入到 samlFilter FilterChainProxy 中,它返回传递给它的相同 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

【讨论】:

  • 这确实解决了这个问题。非常感谢@RobWinch。
【解决方案2】:

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

  1. 创建一个扩展DefaultHttpFirewall的类
  2. 覆盖getFirewalledResponse(HttpResponse response) 方法,替换为检查响应类型的方法;如果是instanceof SaveContextOnUpdateOrErrorResponseWrapper,则返回提供的响应,否则返回return super.getFirewalledResponse()
  3. 使用@RobWinch 的回答中概述的属性注入注入此类的 bean。

此实现更符合 FilterChainProxy 和 DefaultHttpFirewall 的先前行为,因为它只会在类型与容易出错的响应类型匹配时简单地返回传入的响应。否则,调用 super 方法,保留父级的逻辑。此外,getFirewalledRequest(...) 方法的逻辑被保留,因为在这种情况下,这似乎不是错误的根源。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-29
    • 1970-01-01
    • 2012-12-14
    • 2011-01-22
    • 2021-01-28
    • 2023-03-05
    • 1970-01-01
    • 2020-04-08
    相关资源
    最近更新 更多