使用Spring Security 3.2.0.RELEASE,如何在纯HTML的页面中获取CSRF令牌而没有标记库

时间:2013-12-31 19:21:55

标签: spring spring-mvc spring-security

今天我从具有独立java配置依赖性的Spring Security 3.1.4升级到包含java config的新3.2.0版本。默认情况下CSRF处于打开状态,我知道我可以使用“http.csrf()。disable()”在我的重写配置方法中禁用它。但是假设我不想禁用它,但我需要在登录页面上使用CSRF令牌,其中没有使用JSP标记库或Spring标记库。

我的登录页面纯粹是HTML,我在使用Yeoman生成的Backbone应用程序中使用。我如何在表单中或作为标题包含HttpSession中包含的CSRF令牌,这样我就不会得到“未找到预期的CSRF令牌。您的会话是否已过期?”异常?

4 个答案:

答案 0 :(得分:21)

您可以使用the reference中概述的名为_csrf的请求属性获取CSRF。要将CSRF添加到HTML页面,您需要使用JavaScript来获取需要包含在请求中的令牌。

将标记作为标题返回比使用JSON在主体中更安全,因为正文中的JSON可以通过外部域获取。例如,您的JavaScript可以请求由以下处理的URL:

CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
// Spring Security will allow the Token to be included in this header name
response.setHeader("X-CSRF-HEADER", token.getHeaderName());
// Spring Security will allow the token to be included in this parameter name
response.setHeader("X-CSRF-PARAM", token.getParameterName());
// this is the value of the token to be included as either a header or an HTTP parameter
response.setHeader("X-CSRF-TOKEN", token.getToken());

然后,您的JavaScript将从响应标头中获取标头名称或参数名称以及令牌,并将其添加到登录请求中。

答案 1 :(得分:4)

虽然@ rob-winch是对的,但我建议从会话中获取令牌。如果Spring-Security使用SessionManagementFilterCsrfAuthenticationStrategy中生成新令牌,则会将其设置为Session而不是Request。所以你可能会得到错误的csrf令牌。

public static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN");
CsrfToken sessionToken = (CsrfToken) request.getSession().getAttribute(DEFAULT_CSRF_TOKEN_ATTR_NAME);

答案 2 :(得分:3)

注意: 我正在使用CORS和AngularJS。

注意事项2: 我找到了Stateless Spring Security Part 1: Stateless CSRF protection,这对于保持AngularJS处理CSRF的方式很有意思。

我没有使用基于答案的Spring Security CSRF Filter(特别是@Rob Winch的答案),而是使用The Login Page: Angular JS and Spring Security Part II中描述的方法。

除此之外,我还必须添加Access-Control-Allow-Headers: ..., X-CSRF-TOKEN(由于CORS)。

实际上,我发现这种方法比在响应中添加标题更清晰。

以下是代码:

<强> HttpHeaderFilter.java

@Component("httpHeaderFilter")
public class HttpHeaderFilter extends OncePerRequestFilter {
    @Autowired
    private List<HttpHeaderProvider> providerList;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        providerList.forEach(e -> e.filter(request, response));

        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
            response.setStatus(HttpStatus.OK.value());
        }
        else {
            filterChain.doFilter(request, response);
        }
    }
}

<强> HttpHeaderProvider.java

public interface HttpHeaderProvider {
    void filter(HttpServletRequest request, HttpServletResponse response);
}

<强> CsrfHttpHeaderProvider.java

@Component
public class CsrfHttpHeaderProvider implements HttpHeaderProvider {
    @Override
    public void filter(HttpServletRequest request, HttpServletResponse response) {
        response.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "X-CSRF-TOKEN");
    }
}

<强> CsrfTokenFilter.java

@Component("csrfTokenFilter")
public class CsrfTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        CsrfToken csrf = (CsrfToken)request.getAttribute(CsrfToken.class.getName());

        if (csrf != null) {
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");

            String token = csrf.getToken();

            if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                cookie = new Cookie("XSRF-TOKEN", token);
                cookie.setPath("/");

                response.addCookie(cookie);
            }
        }

        filterChain.doFilter(request, response);
    }
}

<强>的web.xml

...
<filter>
    <filter-name>httpHeaderFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <async-supported>true</async-supported>
</filter>
<filter-mapping>
    <filter-name>httpHeaderFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
...

安全-context.xml中

...
<custom-filter ref="csrfTokenFilter" after="CSRF_FILTER"/>
...

<强> app.js

...
.run(['$http', '$cookies', function ($http, $cookies) {
    $http.defaults.transformResponse.unshift(function (data, headers) {
        var csrfToken = $cookies['XSRF-TOKEN'];

        if (!!csrfToken) {
            $http.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken;
        }

        return data;
    });
}]);

答案 3 :(得分:-1)

我在春季靴子上使用百里香。我有同样的问题。我通过浏览器诊断出问题查看返回html的来源。它应该是这样的:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<form method="post" action="/login">
<div><label> User Name : <input type="text" name="username" /> </label></div>
<div><label> Password: <input type="password" name="password" /> </label></div>
<input type="hidden" name="_csrf" value=<!--"aaef0ba0-1c75-4434-b6cf-62c975dcc8ba"--> />
<div><input type="submit" value="Sign In" /></div>
</form>
</body>
</html>

如果你看不到这个HTML代码。您可能忘记在名称和值之前添加th:标记。 <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

<强>的login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}"> Invalid username and password. </div>
<div th:if="${param.logout}"> You have been logged out. </div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>