Spring Security - Remember Me Authentication Error

╄→尐↘猪︶ㄣ 提交于 2019-12-09 19:12:31

问题


We are using Spring MVC and are encountering the following issue related to Remember Me authentication:

  1. User logs in with "Remember Me" checked, works properly, persistent_login table is updated as expected
  2. We restart the app server, perhaps after a deploy, etc
  3. User refreshes page, we see the error msg in Figure 1, user is redirected to login page (does not see error)
  4. Despite error, persistent_login entry token has updated (series remains the same as prior to refresh), spring Remember Me token remains same as well.
  5. User refreshes page a second time, they are logged in like nothing ever happened

Figure 1 -- The error message

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)

Figure 2 -- Security filters

<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"/>

Figure 3 -- Remember Me setup

<!-- 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>

Versions

  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

====== UPDATE ======

We have observed that removing these two SAML-related filters resolves this issue, however we do need these to work...

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

====== UPDATE 2 ======

Details of the samlFilter definition.

<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>

Thanks in advance.


回答1:


This is being caused by the FilterChainProxy being used after the SecurityContextPersistenceFilter. Specifically the FilterChainProxy's HttpFirewall is replacing the HttpServletResponse with a DefaultHttpFirewall which no longer implements the SavedRequest. To get around this, you can inject a custom HttpFirewall into the samlFilter FilterChainProxy that returns the same HttpServletResponse that is passed into it. For example:

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() {}
    }
}

You can then wire it using:

<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>

I have logged a ticket to make this work transparently in the future https://jira.spring.io/browse/SEC-2578




回答2:


I ran into this problem as well. My solution is inspired from @RobWinch's answer but using a perhaps safer implementation:

  1. create a class that extends DefaultHttpFirewall
  2. Override the getFirewalledResponse(HttpResponse response) method, replacing with one that checks the type of the response; if it is instanceof SaveContextOnUpdateOrErrorResponseWrapper, then trivially return the supplied response, and otherwise return return super.getFirewalledResponse().
  3. Inject a bean of this class using the property injection outlined in @RobWinch's answer.

This implementation is more consistent with the prior behavior of the FilterChainProxy and DefaultHttpFirewall as it will only trivially return the passed-in response when the type matches the error-prone response type. Otherwise, the super method is called, preserving the parent's logic. Also, the logic of the getFirewalledRequest(...) method is preserved, since this does not seem to be the source of the error in this case.



来源:https://stackoverflow.com/questions/23272157/spring-security-remember-me-authentication-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!