身份验证后,Spring Security(3.2.5)HTTP POST无法转发到原始请求


问题内容

我有一个受Spring安全性保护的示例Spring
MVC应用程序(Spring版本4.0.1.RELEASE,Spring安全性3.2.5.RELEASE。当我以未经身份验证的用户身份发送HTTP
GET请求时,我将被发送到登录页面(如预期),在我进行身份验证之后,我将被发送到原始GET中请求的页面。

当我以未经身份验证的用户身份发送HTTP POST请求时,将被发送到登录页面(如预期的那样),但是在成功身份验证之后,我将被发送到“ default-
target-url”中指定的页面,而不是页面按照我的原始POST请求中的要求。

当我以经过身份验证的用户身份尝试相同的HTTP POST时,它工作正常(按预期方式)。我已经尝试设置always-use-default-target =“
false”以及完全省略该属性,并且行为相同。

我想念什么吗?Spring应该在身份验证之后传递POST请求,还是出于某种原因而设计不这样做?

这是我的Spring安全配置:

    <beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/security
    http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <http auto-config="true">
        <intercept-url pattern="/admin/**" access="ROLE_USER" />
        <form-login 
            login-page="/login.htm" 
            default-target-url="/hello.htm" 
            always-use-default-target="false"
            authentication-failure-url="/login.htm?error" 
            username-parameter="username"
            password-parameter="password" />
        <logout logout-success-url="/login.htm?logout" />
        <!-- enable csrf protection -->
        <csrf/>
    </http>

    <authentication-manager>
      <authentication-provider>
        <user-service>
        <user name="admin" password="password" authorities="ROLE_USER" />
        <user name="super" password="man" authorities="ROLE_SUPER_USER" />
        </user-service>
      </authentication-provider>
    </authentication-manager>

</beans:beans>

这是我用来启动测试的jsp(测试GET的链接和测试POST的表单):

<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
  <head><title>TEST SECURITY</title></head>
  <body>
        <p><a href="admin/security_landing.htm">GET</a></p>
        <form:form  method="POST" action="admin/security_landing.htm"><input type="submit" value="POST"></form:form>

  </body>
</html>

这是着陆页,它是一种受保护的资源:

<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<html>
  <head><title>TEST SECURITY LANDING PAGE</title></head>
  <body>
        <p>YOU MADE IT!!!!</p>
  </body>
</html>

这是我的测试控制器:

package springapp.web;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


@Controller
public class TestController {

    /** Logger for this class and subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    @RequestMapping(value="test", method= RequestMethod.GET)
    public ModelAndView methodGet()
            {

        logger.info("Found it's way to the GET method");

        ModelAndView model = new ModelAndView();

        model.setViewName("security_test");

        return model;

    }

    @RequestMapping(value="/admin/security_landing", method=RequestMethod.POST)
    public ModelAndView sendToLandingPOST()
            {

        logger.info("Found it's way to the GET method");

        ModelAndView model = new ModelAndView();

        model.setViewName("/admin/security_landing");

        return model;

    }

    @RequestMapping(value="/admin/security_landing", method= RequestMethod.GET)
    public ModelAndView sendToLandingGET()
            {

        logger.info("Found it's way to the GET method");

        ModelAndView model = new ModelAndView();

        model.setViewName("/admin/security_landing");

        return model;

    }
}

如果相关的话,我可以包含更多的Spring配置,但是在该应用程序可以很好地使用GET的情况下,但在POST中却表现不佳(我认为),我认为它与我在此处展示的部分无关。

在我看来,Spring安全性应该能够像GET一样拦截POST并在身份验证后通过POST。

任何提示或帮助,将不胜感激。谢谢,罗伯


问题答案:

如前所述,启用CSRF后,Spring
Security将仅保存GET请求。原因是一旦用户认证,CSRF令牌就会更改,以防止恶意用户在用户认证之前(即在公共环境中)发现CSRF。如果我们缓存了请求,那么它将使用旧的CSRF进行重放,并且无论如何都会使CSRF验证失败。

通常,保存POST请求并自动处理它似乎有些危险。考虑公共计算机包含对常用站点的访问的情况。恶意用户对未经身份验证的应用程序执行POST,该应用程序将资金从当前经过身份验证的用户转移到其银行帐户。应用程序将缓存POST,然后显示登录表单。恶意用户走开,受害者看到登录页面已经存在。此外,浏览器中的URL可以通过HTTPS正确显示。受害者登录后,最初请求的POST请求将被重播,并自动将资金从受害者转移到恶意用户。

相反,我们可能应该确保在要重播POST时显示中间页面。