使用 Spring Security 3.2.0.RELEASE,如何在没有标签库的纯 HTML 页面中获取 CSRF 令牌
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20862299/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
With Spring Security 3.2.0.RELEASE, how can I get the CSRF token in a page that is purely HTML with no tag libs
提问by Patrick Grimard
Today I upgraded from Spring Security 3.1.4 with the separate java config dependency, to the new 3.2.0 release which includes java config. CSRF is on by default and I know I can disable it in my overridden configure method with "http.csrf().disable()". But suppose I don't want to disable it, but I need the CSRF token on my login page where no JSP tag libs or Spring tag libs are being used.
今天,我从具有独立 java 配置依赖项的 Spring Security 3.1.4 升级到包含 java 配置的新 3.2.0 版本。CSRF 默认开启,我知道我可以在我的覆盖配置方法中使用“http.csrf().disable()”禁用它。但是假设我不想禁用它,但我需要在我的登录页面上使用 CSRF 令牌,其中没有使用 JSP 标记库或 Spring 标记库。
My login page is purely HTML that I use in a Backbone app that I've generated using Yeoman. How would I go about including the CSRF token that's contained in the HttpSession in either the form or as a header so that I don't get the "Expected CSRF token not found. Has your session expired?" exception?
我的登录页面纯粹是 HTML,我在使用 Yeoman 生成的 Backbone 应用程序中使用了它。我将如何将包含在 HttpSession 中的 CSRF 令牌包含在表单或标题中,以便我不会收到“未找到预期的 CSRF 令牌。您的会话是否已过期?” 例外?
回答by Rob Winch
You can obtain the CSRF using the request attribute named _csrf as outlined in the reference. To add the CSRF to an HTML page, you will need to use JavaScript to obtain the token that needs to be included in the requests.
您可以使用参考中概述的名为 _csrf 的请求属性获取 CSRF 。要将 CSRF 添加到 HTML 页面,您需要使用 JavaScript 获取需要包含在请求中的令牌。
It is safer to return the token as a header than in the body as JSON since JSON in the body could be obtained by external domains. For example your JavaScript could request a URL processed by the following:
将令牌作为标头返回比在正文中作为 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());
Your JavaScript would then obtain the header name or the parameter name and the token from the response header and add it to the login request.
然后,您的 JavaScript 将从响应标头中获取标头名称或参数名称和令牌,并将其添加到登录请求中。
回答by bsmk
Although @rob-winch is right I would suggest to take token from session. If Spring-Security generates new token in SessionManagementFilter
using CsrfAuthenticationStrategy
it will set it to Session but not on Request. So it is possible you will end up with wrong csrf token.
虽然@rob-winch 是对的,但我建议从会话中获取令牌。如果 Spring-Security 在SessionManagementFilter
使用中生成新令牌,CsrfAuthenticationStrategy
它将设置为 Session 而不是请求。因此,您最终可能会得到错误的 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);
回答by Ludovic Guillaume
Note:I'm using CORS and AngularJS.
注意:我使用的是 CORS 和 AngularJS。
Note2:I found Stateless Spring Security Part 1: Stateless CSRF protectionwhich would be interesting to keep the AngularJS' way to handle CSRF.
注 2:我发现无状态 Spring 安全第 1 部分:无状态 CSRF 保护,这对于保持 AngularJS 处理 CSRF 的方式很有趣。
Instead of using Spring Security CSRF Filterwhich is based on answers (especially @Rob Winch's one), I used the method described in The Login Page: Angular JS and Spring Security Part II.
我没有使用基于答案的Spring Security CSRF 过滤器(尤其是 @Rob Winch 的过滤器),而是使用了登录页面:Angular JS 和 Spring Security Part II 中描述的方法。
In addition to this, I had to add Access-Control-Allow-Headers: ..., X-CSRF-TOKEN
(due to CORS).
除此之外,我还必须添加Access-Control-Allow-Headers: ..., X-CSRF-TOKEN
(由于 CORS)。
Actually, I find this method cleaner than adding headers to the response.
实际上,我发现这种方法比向响应添加标头更简洁。
Here is the code :
这是代码:
HttpHeaderFilter.java
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
HttpHeaderProvider.java
public interface HttpHeaderProvider {
void filter(HttpServletRequest request, HttpServletResponse response);
}
CsrfHttpHeaderProvider.java
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
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
网页.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>
...
security-context.xml
安全上下文.xml
...
<custom-filter ref="csrfTokenFilter" after="CSRF_FILTER"/>
...
app.js
应用程序.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;
});
}]);
回答by Iren Saltal?
I use thymeleaf with Spring boot. I had the same problem. I diagnosed problem viewing source of returned html via browser. It should be look like this:
我将 thymeleaf 与 Spring Boot 一起使用。我有同样的问题。我诊断出通过浏览器查看返回的 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>
If you can't see this html code. You may be forgot to put th:
tag before name and value. <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
如果您看不到此 html 代码。您可能忘记th:
在名称和值之前放置标签。<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
login.html
登录.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>