Session timeout leads to Access Denied in Spring MVC when CSRF integration with Spring Security

前端 未结 2 1070
终归单人心
终归单人心 2021-01-02 04:46

I have Integrated CSRF token with Spring Security in my Spring MVC Project. Everything work properly with CSRF token, token will be send from client side to server side. <

相关标签:
2条回答
  • The answer provided by mdrg is right on, and I also implemented a custom AccessDeniedHandler which I submit for your consideration:

    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandlerImpl;
    import org.springframework.security.web.csrf.MissingCsrfTokenException;
    import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
    import org.springframework.security.web.savedrequest.RequestCache;
    
    /**
     * Intended to fix the CSRF Timeout Caveat 
     * (https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf-timeouts).
     * When the session expires and a request requiring CSRF is received (POST), the
     * missing token exception is handled by caching the current request and 
     * redirecting the user to the login page after which their original request will
     * complete. The intended result is that no loss of data due to the timeout will
     * occur.
     */
    public class MissingCsrfTokenAccessDeniedHandler extends AccessDeniedHandlerImpl {
      private RequestCache requestCache = new HttpSessionRequestCache();
      private String loginPage = "/login";
    
      @Override
      public void handle(HttpServletRequest req, HttpServletResponse res, AccessDeniedException exception) throws IOException, ServletException {
        if (exception instanceof MissingCsrfTokenException && isSessionInvalid(req)) {
          requestCache.saveRequest(req, res);
          res.sendRedirect(req.getContextPath() + loginPage);
        }
        super.handle(req, res, exception);
      }
    
      private boolean isSessionInvalid(HttpServletRequest req) {
        try {
          HttpSession session = req.getSession(false);
          return session == null || !req.isRequestedSessionIdValid();
        }
        catch (IllegalStateException ex) {
          return true;
        }
      }
    
      public void setRequestCache(RequestCache requestCache) {
        this.requestCache = requestCache;
      }
    
      public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
      }
    } 
    

    Wired up via java config:

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
        ...
        http.exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
        ...
      }
    
       public AccessDeniedHandler getAccessDeniedHandler() {
        return new MissingCsrfTokenAccessDeniedHandler();
      }
    }
    
    0 讨论(0)
  • 2021-01-02 05:36

    The question is a bit old, but answers are always useful.

    First, this is a known issue with session-backed CSRF tokens, as described in the docs: CSRF Caveats - Timeouts.

    To solve it, use some Javascript to detect imminent timeouts, use a session-independent CSRF token repository or create a custom AccessDeniedHandler route. I chose the latter:

    Config XML:

    <http>
        <!-- ... -->
        <access-denied-handler ref="myAccessDeniedHandler"/>
    </http>
    
    <bean id="myAccessDeniedHandler" class="package.MyAccessDeniedHandler">
        <!-- <constructor-arg ref="myInvalidSessionStrategy" /> -->
    </bean>
    

    MyAccessDeniedHandler:

    public class MyAccessDeniedHandler implements AccessDeniedHandler {
        /* ... */
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception)
                throws IOException, ServletException {
            if (exception instanceof MissingCsrfTokenException) {
                /* Handle as a session timeout (redirect, etc).
                Even better if you inject the InvalidSessionStrategy
                used by your SessionManagementFilter, like this:
                invalidSessionStrategy.onInvalidSessionDetected(request, response);
                */
            } else {
                /* Redirect to a error page, send HTTP 403, etc. */
            }
        }
    }
    

    Alternatively, you can define the custom handler as a DelegatingAccessDeniedHandler:

    <bean id="myAccessDeniedHandler" class="org.springframework.security.web.access.DelegatingAccessDeniedHandler">
        <constructor-arg name="handlers">
            <map>
                <entry key="org.springframework.security.web.csrf.MissingCsrfTokenException">
                    <bean class="org.springframework.security.web.session.InvalidSessionAccessDeniedHandler">
                        <constructor-arg name="invalidSessionStrategy" ref="myInvalidSessionStrategy" />
                    </bean>
                </entry>
            </map>
        </constructor-arg>
        <constructor-arg name="defaultHandler">
            <bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/my_error_page"/>
            </bean>
        </constructor-arg>
    </bean>
    
    0 讨论(0)
提交回复
热议问题