С Spring Security 3.2.0.RELEASE, как я могу получить токен CSRF на странице, которая является чисто HTML без тегов libs
Сегодня я обновился от Spring Security 3.1.4 с отдельной зависимостью java-конфигурации до новой версии 3.2.0, которая включает в себя java-конфигурацию. CSRF включен по умолчанию, и я знаю, что могу отключить его в моем переопределенном методе configure с помощью "http.csrf(). Disable()". Но предположим, что я не хочу его отключать, но мне нужен токен CSRF на моей странице входа, где не используются теги JSP tag или Spring.
Моя страница входа - это чисто HTML, который я использую в приложении Backbone, которое я создал с помощью Yeoman. Как я мог бы включить токен CSRF, содержащийся в HttpSession, в форме или в виде заголовка, чтобы я не получил "ожидаемый токен CSRF не найден. Срок действия вашего сеанса истек"? исключение?
Ответы
Ответ 1
Вы можете получить CSRF, используя атрибут request с именем _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 получит имя заголовка или имя параметра и токен из заголовка ответа и добавит его в запрос на вход.
Ответ 2
Хотя @rob-winch прав, я бы предложил взять токен из сеанса. Если Spring -Security генерирует новый токен в SessionManagementFilter
, используя CsrfAuthenticationStrategy
, он установит его на сеанс, но не на запрос. Таким образом, возможно, вы закончите с неправильным токеном 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);
Ответ 3
Примечание: Я использую CORS и AngularJS.
Примечание2: Я нашел Stateless Spring Security Part 1: защита без CSRF без сохранения состояния, которая была бы интересна для сохранения метод AngularJS для обработки CSRF.
Вместо использования Spring Security CSRF Filter, основанного на ответах (особенно @Rob Winch one), я использовал метод, описанный в Страница входа: Angular JS и Spring Часть защиты безопасности.
В дополнение к этому мне пришлось добавить 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;
});
}]);
Ответ 4
Я использую тимелеар с загрузкой Spring. У меня такая же проблема. Я диагностировал проблему просмотра источника возвращенного 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>
Ответ 5
Разве это сообщение не отвечает на вопрос?
http://spring.io/blog/2013/08/21/spring-security-3-2-0-rc1-highlights-csrf-protection
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>