Я пытался добавить защиту CSRF / XSRF в свое приложение, но столкнулся со странным поведением. Все запросы на получение работают нормально, но на все посты / вставки / удаления я получаю 403 Unauthorized. И самое странное, что когда я пытался отладить мой CSRF-фильтр, запросы не доходят до него, они отклоняются где-то раньше. Они даже не доходят до моего фильтра аутентификации, поэтому я не могу понять, в чем проблема.

Мой конфиг безопасности:

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    ...
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
                    .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
                    .csrf().csrfTokenRepository(csrfTokenRepository());
        }

        private CsrfTokenRepository csrfTokenRepository() {
            HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
            repository.setHeaderName("X-XSRF-TOKEN");
            return repository;
        }

Я не добавляю фильтры, поскольку, как я уже сказал, запросы не доходят до них. Но при необходимости я закончу свой вопрос. Надеюсь на вашу помощь, заранее спасибо!

0
Anar Sultanov 9 Сен 2017 в 12:26

3 ответа

Лучший ответ

Большое спасибо за ответы, они действительно помогли мне найти решение. И я хочу поделиться своим решением, если в будущем кто-то столкнется с той же проблемой.

Как отмечалось в ответах, я использовал SessionCreationPolicy.STATELESS и не имел сессий, поэтому вместо HttpSessionCsrfTokenRepository мне пришлось использовать CookieCsrfTokenRepository с withHttpOnlyFalse(), чтобы AngularJS мог читать куки.

В результате у меня есть такая конфигурация:

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            ...
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
            .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
            .csrf().csrfTokenRepository(csrfTokenRepository());
}

Если кого-то интересует, как выглядит CsrfHeaderFilter:

public class CsrfHeaderFilter 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);
    }
}

Моя вторая проблема была CORS. Документация AngularJS гласит:

«Заголовок не будет установлен для междоменных запросов».

Чтобы решить эту проблему, мне пришлось использовать HTTP-перехватчик:

.factory('XsrfInterceptor', function ($cookies) {
  return {
    request: function (config) {
      var headerName = 'X-XSRF-TOKEN';
      var cookieName = 'XSRF-TOKEN';
      config.headers[headerName] = $cookies.get(cookieName);
      return config;
    }
  };
});

.config(['$httpProvider', function($httpProvider) {  
    $httpProvider.interceptors.push('XsrfInterceptor');
}]);

Я надеюсь, что мой ответ будет полезен.

1
Anar Sultanov 9 Сен 2017 в 22:03

В принципе, механизм CSRF в Spring хранит токен CSRF в файле cookie только HTTP. Поскольку JavaScript не может получить доступ только к файлам cookie HTTP, необходимо указать Spring, чтобы отключить только HTTP:

.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

Затем вы можете прочитать файл cookie из Angular и добавить его в заголовок XSRF-TOKEN при каждом запросе.

Это общий случай. Я не уверен, подходит ли это вашему особому случаю.

2
zakaria amine 9 Сен 2017 в 14:25

Предполагая, что остальные ваши конфиг / фильтры работают правильно, вы столкнулись с этой проблемой из-за этого: SessionCreationPolicy.STATELESS.

Вы можете заглянуть под капот Spring ({X0}}). Вы увидите, что он должен помнить значение каждого CSRF-токена для каждого пользователя в сеансе, и, поскольку вы не используете сеансы, это невозможно сделать.

Что делать дальше - действительно решать вам. Некоторые люди говорят, что если ваше приложение не имеет состояния, на самом деле нет необходимости в защите CSRF. Spring Docs говорит, что CSRF-атаки все еще актуальны. Я думаю, что это действительно зависит от вашего механизма аутентификации.

Вы также можете посмотреть на Это хорошая статья, например.

Надеюсь, это поможет.

0
Leffchik 9 Сен 2017 в 10:13