Маркер CSRF не генерируется с помощью Webflux - PullRequest
0 голосов
/ 04 ноября 2018

У меня есть приложение Webflux, защищенное Spring Security, в котором защита CSRF включена по умолчанию. Однако я не могу получить маркер CSRF для сохранения в сеансе.

После некоторых исследований я заметил, что это может быть от WebSessionServerCsrfTokenRepository.class. В этом классе есть метод generateToken, который должен создать Mono из сгенерированного токена CSRF:

public Mono<CsrfToken> generateToken(ServerWebExchange exchange) {
        return Mono.fromCallable(() -> {
            return this.createCsrfToken();
        });
    }

private CsrfToken createCsrfToken() {
        return new DefaultCsrfToken(this.headerName, this.parameterName, this.createNewToken());
    }

    private String createNewToken() {
        return UUID.randomUUID().toString();
    }

Однако, даже если метод generateToken вызывается CsrfWebFilter, метод createCsrfToken никогда не вызывается, и я никогда не получаю токен CSRF, который будет сохранен в сеансе. Моя точка останова никогда не переходит в метод createCsrfToken, это может означать, что он никогда не подписывается.

Я работаю на Netty с Spring Boot 2.1.0.RELEASE и Spring Security 5.1.1.RELEASE.

Я воспроизвел проблему в пустом примере приложения, просто содержащем следующие зависимости:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>

Я что-то упустил или есть проблема с Spring Security?

UPDATE

Из дальнейших исследований я думаю, что проблема связана с этим методом в Spring Security CsrfWebFilter.class:

private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(() -> {
            Mono<CsrfToken> csrfToken = this.csrfToken(exchange);
            exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken);
            return chain.filter(exchange);
        });
    }

Здесь, csrfToken Mono никогда не подписывается. Когда я переписываю фильтр таким образом, мне удается добавить токен в сеансе:

private Mono<Void> continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) {
        return Mono.defer(() -> {
            return this.csrfToken(exchange)
                    .map(csrfToken -> exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken))
                    .then(chain.filter(exchange));
        });
    }

Однако параметр _csrf никогда не добавляется в мою модель Thymeleaf, поэтому следующий тест не работает:

<form name="test-csrf" action="/test" method="post">
            <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
            <button type="submit">Escape!</button>
        </form>

1 Ответ

0 голосов
/ 14 ноября 2018

На случай, если кто-нибудь столкнется с этой проблемой, я поговорил с кем-то из Spring Team.

На самом деле он предназначен не подписываться на csrfToken Mono напрямую, чтобы делать это только при необходимости. Ответственность за инициирование подписки в приложении лежит на разработчике, и это можно сделать двумя способами.

Способ 1: явная подписка.

Предоставьте подписку через @ ModelAttribute в некоторых @ ControllerAdvice или абстрактном классе контроллера:

@ModelAttribute(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME)
    public Mono<CsrfToken> getCsrfToken(final ServerWebExchange exchange) {

        return exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
    }

Метод 2: используйте Thymeleaf для автоматической обработки CSRF.

Убедитесь, что в вашем POM есть следующие зависимости для использования Thymeleaf вместе с Spring Security:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

Это автоматически добавит токен CSRF в вашу модель и пропустит его через скрытые входные данные форм (все еще необходимо добавить его в заголовки для запроса POST Ajax).

Для получения дополнительной информации вот проблема, которую я открыл весной: https://github.com/spring-projects/spring-security/issues/6046

...