CSRF 与 Spring Security 集成时,会话超时导致 Spring MVC 中的访问被拒绝

问题描述 投票:0回答:2

我在 Spring MVC 项目中将 CSRF 令牌与 Spring Security 集成。使用 CSRF 令牌一切正常,令牌将从客户端发送到服务器端。

我已经更改了我的

logout
流程,使其成为发送 CSRF 令牌的
POST
方法,并且其工作正常。

发生会话超时时,我遇到问题,需要将其重定向到 spring 默认注销 URL,但它在该 URL 上给了我

Access Denied

如何覆盖此行为。

我在安全配置文件中包含以下行

   <http>
         //Other config parameters
        <csrf/>
   </http>

如果有人需要更多信息,请告诉我。

spring spring-mvc spring-security csrf-protection
2个回答
16
投票

问题有点老了,但答案总是有用的。

首先,这是会话支持的 CSRF 令牌的一个已知问题,如文档中所述:CSRF 警告 - 超时

要解决此问题,请使用一些 Javascript 来检测即将发生的超时,使用独立于会话的 CSRF 令牌存储库或创建自定义

AccessDeniedHandler
路由。我选择了后者:

配置 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. */
        }
    }
}

或者,您可以将自定义处理程序定义为

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>

4
投票

mdrg 提供的答案是正确的,我还实现了一个自定义

AccessDeniedHandler
,我提交供您考虑:

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

通过java配置连接:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    ...
    http.exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    ...
  }

   public AccessDeniedHandler getAccessDeniedHandler() {
    return new MissingCsrfTokenAccessDeniedHandler();
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.