Spring CSRF令牌在登录期间过期

Spring CSRF令牌在登录期间过期,spring,spring-security,csrf,Spring,Spring Security,Csrf,我正在开发SpringWeb应用程序,我需要避免登录页面上的过期csrf令牌问题,因为若用户等待太长时间并尝试登录,解决csrf问题的唯一方法是重新加载页面并再次尝试登录。但是它对用户不友好,我想避免这种情况 第一个问题:一般来说是否可能(通过spring security 3.2.4)?无需禁用csrf 我尝试对登录页面使用security=“none”和spring security“login\u check”,但它不起作用,我得到了无限重定向,或者我得到了错误,url“myhost/lo

我正在开发SpringWeb应用程序,我需要避免登录页面上的过期csrf令牌问题,因为若用户等待太长时间并尝试登录,解决csrf问题的唯一方法是重新加载页面并再次尝试登录。但是它对用户不友好,我想避免这种情况

第一个问题:一般来说是否可能(通过spring security 3.2.4)?无需禁用csrf

我尝试对登录页面使用security=“none”和spring security“login\u check”,但它不起作用,我得到了无限重定向,或者我得到了错误,url“myhost/login\u check”没有映射

第二个问题:我怎么做?

推荐的解决方案 我想说的是,您不应该在生产站点上禁用csrf令牌。您可以使会话(以及csrf令牌)持续更长时间(但通常不会持续超过一天,尤其是对于未登录的用户,因为它是DOS向量),但真正的解决方案可能是在csrf令牌过期时自动刷新登录页面。你可以使用

<META HTTP-EQUIV="REFRESH" CONTENT="csrf_timeout_in_seconds">
但是请注意,您需要真正挖掘并覆盖spring安全内部机制,即:

  • 如果请求到达且不存在匿名会话,则动态重新创建匿名会话
  • 从会话id动态重新创建csrf令牌
并选择一种非常安全的哈希算法,最好是sha-512

第三种解决方案 您可以使用一个小javascript定期(就在会话超时之前)在服务器上调用no-op页面,从而扩展会话。只有在浏览器一直打开的情况下,才会导致无限会话超时,因此DOS方面的问题会得到缓解

好的,最后一个解决方案 您可以更改CSRF令牌检查代码,并在登录页面中将其禁用。这实际上是第二种解决方案的同义词,但对于登录页面是特定的,而不是所有匿名会话

您可以这样做,例如通过在HttpSecurity中设置自定义RequestMatcher:

http.csrf().requireCsrfProtectionMatcher(new MyCsrfRequestMatcher());
...
class MyCsrfRequestMatcher implements RequestMatcher {
    @Override
    public boolean matches(HttpServletRequest request) {
        return !request.getServletPath().equals("/login");
    }
}

另一个选项是默认情况下为会话设置无超时,然后,当用户通过身份验证时,将超时更改为您想要的任何值。您可以看到如何执行此操作的示例。

您还可以使您的CSRF保护依赖于cookie,而不是服务器端会话状态。Spring Security对此有充分的支持

只有当cookie过期时,您才会收到超时。这可以很好地扩展,因为它基本上是无状态的(从服务器的角度来看)


Andrew

在我参与的一个项目中,我实施了以下内容:

  • 实现一个异常处理程序来处理CsrfException(在我的例子中通常是AccessDeniedException)。将请求转发给控制器方法

    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    public void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
        request.getRequestDispatcher("/Access_Denied").forward(request, response);
    }
    
  • 在控制器方法中,检查原始请求是否用于登录页面。如果是,请在登录页面中显示适当的消息

    if ("/login".equals(request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH))) {
        model.addAttribute("error", "An invalid security token has been detected. Please try again.");
        return "login.jsp";
    } else {
        return "accessDenied.jsp";
    }
    

  • 使用这种方法,用户将能够在不需要刷新的情况下重试登录。

    这是与重新加载页面相同的解决方案,可以工作,但对用户不友好。但是用户不必手动重新加载页面。很可能用户甚至不会注意到页面被重新加载,如果他没有接触页面数小时,所以我认为这是一个非发布。如果你的会话持续下去,那将是一个DoS攻击向量(因为你需要内存/磁盘来存储那些会话和CSRF令牌)。但是你可以这样做,如果一个小的停机机会对你的用户来说比在后台重新加载更不方便的话……我想会话的无限超时(或者非常长)是最糟糕的解决方案。但重新加载页面并不完美。但如果您没有无限超时,不续订会话,也不禁用csrf,那么如何在会话超时后获得有效的csrf令牌?应该补充的是,spring文档将存储在Cookie中的csrf令牌称为潜在的不安全令牌,因此,默认情况下会将令牌存储在会话中。看见
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    public void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
        request.getRequestDispatcher("/Access_Denied").forward(request, response);
    }
    
    if ("/login".equals(request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH))) {
        model.addAttribute("error", "An invalid security token has been detected. Please try again.");
        return "login.jsp";
    } else {
        return "accessDenied.jsp";
    }