Подтвердить что ты не робот

Как работает цепочка фильтров безопасности Spring

Я понимаю, что защита Spring построена на цепочке фильтров, которая будет перехватывать запрос, обнаруживать (отсутствие) аутентификацию, перенаправлять на точку входа в аутентификацию или передавать запрос в службу авторизации, и в конечном итоге позволить сервлета или исключение безопасности броузера (неавторизованный или неавторизованный). DelegatingFitlerProxy склеивает эти фильтры вместе. Для выполнения своих задач эти службы доступа к фильтрам, такие как UserDetailsService и AuthenticationManager.

Ключевые фильтры в цепочке (в порядке)

  • SecurityContextPersistenceFilter (восстанавливает аутентификацию из JSESSIONID)
  • UsernamePasswordAuthenticationFilter (выполняет аутентификацию)
  • ExceptionTranslationFilter (исключение безопасности catch из фильтра FilterSecurityInterceptor)
  • FilterSecurityInterceptor (может вызывать исключения для аутентификации и авторизации)

Я запутался, как эти фильтры используются. Является ли это для Spring предоставленной формы входа, UsernamePasswordAuthenticationFilter используется только для /login, а последние фильтры - нет? Использует ли элемент пространства имен формы-входа автоматическую настройку этих фильтров? Предоставляет ли каждый запрос (заверенный или завершенный) фильтр FilterSecurityInterceptor для URL-адреса, не входящего в систему?

Что делать, если я хочу защитить свой REST API с помощью JWT-токена, который извлекается из входа? Я должен настроить два тега http для определения пространства имен, прав? Другой для /login с UsernamePasswordAuthenticationFilter, а другой для URL REST, с пользовательским JwtAuthenticationFilter.

Создает ли два элемента http два springSecurityFitlerChains? По умолчанию отключен UsernamePasswordAuthenticationFilter, пока я не объявлю form-login? Как заменить SecurityContextPersistenceFilter на один, который получит Authentication из существующего JWT-token, а не JSESSIONID?

4b9b3361

Ответ 1

Цепь защиты Spring - очень сложный и гибкий механизм.

Ключевые фильтры в цепочке (в порядке)

  • SecurityContextPersistenceFilter (восстанавливает аутентификацию из JSESSIONID)
  • UsernamePasswordAuthenticationFilter (выполняет аутентификацию)
  • ExceptionTranslationFilter (исключение безопасности catch из фильтра FilterSecurityInterceptor)
  • FilterSecurityInterceptor (может вызывать исключения для аутентификации и авторизации)

Глядя на текущую стабильную версию 4.2.1 документацию, раздел 13.3 Filter Ordering вы можете увидеть всю организацию фильтра цепочки фильтров:

13.3 Сортировка фильтров

Порядок, в котором фильтры определены в цепочке, очень важен. Независимо от того, какие фильтры вы используете, заказ должен быть следующим:

  • ChannelProcessingFilter, потому что ему может потребоваться перенаправить на другой протокол

  • SecurityContextPersistenceFilter, поэтому SecurityContext может быть настроен в SecurityContextHolder в начале веб-запроса и любые изменения в SecurityContext могут быть скопированы на HttpSession когда веб-запрос заканчивается (готов к использованию со следующим веб-запросом)

  • ConcurrentSessionFilter, поскольку он использует функциональность SecurityContextHolder и нуждается в обновлении SessionRegistry для отражения текущих запросов от основного

  • Механизмы аутентификации - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter и т.д., чтобы SecurityContextHolder мог быть изменен, чтобы содержать допустимый токен запроса аутентификации

  • SecurityContextHolderAwareRequestFilter, если вы используете его для установки Spring Security HttpServletRequestWrapper в свой контейнер сервлетов

  • JaasApiIntegrationFilter, если JaasAuthenticationToken находится в SecurityContextHolder, это обработает FilterChain как Тема в JaasAuthenticationToken

  • RememberMeAuthenticationFilter, так что если ранее механизм проверки подлинности не обновил SecurityContextHolder, и в запросе представлен файл cookie, который позволяет использовать службы mem-me для произойдет, будет сохранен подходящий запоминаемый объект аутентификации есть

  • AnonymousAuthenticationFilter, так что если ранее механизм проверки подлинности не обновил SecurityContextHolder, анонимный объект аутентификации будет помещен туда

  • ExceptionTranslationFilter, чтобы поймать любые исключения безопасности Spring, чтобы можно было вернуть ответ об ошибке HTTP или может быть запущен соответствующий AuthenticationEntryPoint

  • FilterSecurityInterceptor, чтобы защитить веб-URI и создавать исключения, когда доступ запрещен

Теперь, я постараюсь ответить на ваши вопросы один за другим:

Я запутался, как эти фильтры используются. Это для springпри условии, что форма-login, UsernamePasswordAuthenticationFilter используется только для /login, а последние фильтры нет? Имеет ли пространство имен формы элемент автоматически настраивать эти фильтры? Каждый запрос (заверенные или не завершенные) доходят до фильтра FilterSecurityInterceptor для не-входа URL?

Как только вы настраиваете раздел <security-http>, для каждого из них вы должны хотя бы предоставить один механизм аутентификации. Это должен быть один из фильтров, которые соответствуют группе 4 в разделе 13.3 "Сортировка фильтров" из документации по безопасности Spring, на которую я только что ссылался.

Это минимальный допустимый элемент безопасности: http, который можно настроить:

<security:http authentication-manager-ref="mainAuthenticationManager" 
               entry-point-ref="serviceAccessDeniedHandler">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>

Просто сделав это, эти фильтры настроены в прокси-сервере фильтра:

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "8": "org.springframework.security.web.session.SessionManagementFilter",
        "9": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

Примечание. Я получаю их, создавая простой RestController, который @Autowires FilterChainProxy и возвращает его содержимое:

    @Autowired
    private FilterChainProxy filterChainProxy;

    @Override
    @RequestMapping("/filterChain")
    public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        return this.getSecurityFilterChainProxy();
    }

    public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
        int i = 1;
        for(SecurityFilterChain secfc :  this.filterChainProxy.getFilterChains()){
            //filters.put(i++, secfc.getClass().getName());
            Map<Integer, String> filters = new HashMap<Integer, String>();
            int j = 1;
            for(Filter filter : secfc.getFilters()){
                filters.put(j++, filter.getClass().getName());
            }
            filterChains.put(i++, filters);
        }
        return filterChains;
    }

Здесь мы могли видеть, что просто объявив элемент <security:http> с одной минимальной конфигурацией, включены все фильтры по умолчанию, но ни один из них не имеет тип аутентификации (4-я группа в разделе 13.3 "Сортировка фильтра" ). Таким образом, это фактически означает, что просто объявив элемент security:http, SecurityContextPersistenceFilter, ExceptionTranslationFilter и FilterSecurityInterceptor настроены автоматически.

Фактически, должен быть настроен один механизм обработки аутентификации и даже пространство имен безопасности beans обрабатывает претензии для этого, вызывая ошибку во время запуска, но его можно обойти, добавив атрибут точки входа-ref в <http:security>

Если я добавлю базовую <form-login> в конфигурацию, следующим образом:

<security:http authentication-manager-ref="mainAuthenticationManager">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
</security:http>

Теперь filterChain будет выглядеть следующим образом:

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
        "6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
        "7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "10": "org.springframework.security.web.session.SessionManagementFilter",
        "11": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

Теперь эти два фильтра org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter и org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter создаются и настраиваются в FilterChainProxy.

Итак, теперь вопросы:

Является ли это для Spring предоставленной формы входа, UsernamePasswordAuthenticationFilter используется только для /login и последние фильтры не являются?

Да, он используется, чтобы попытаться завершить механизм обработки входа в случае, если запрос соответствует URL-адресу UsernamePasswordAuthenticationFilter. Этот URL-адрес может быть настроен или даже изменен, чтобы соответствовать каждому запросу.

У вас также может быть несколько механизмов обработки аутентификации, настроенных в том же FilterchainProxy (например, HttpBasic, CAS и т.д.).

Элемент пространства имен формы-входа автоматически настраивает эти фильтры?

Нет, элемент form-login настраивает UserPasswordAUthenticationFilter, и в случае, если вы не указываете URL-адрес для входа в систему, он также настраивает org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, который заканчивается простая автозапускаемая страница входа.

Другие фильтры автоматически настроены по умолчанию, просто создав элемент <security:http> без атрибута security:"none".

Поддерживает ли каждый запрос (аутентифицированный или нет) фильтр FilterSecurityInterceptor для URL-адреса, не входящего в систему?

Каждый запрос должен доходить до него, поскольку это элемент, который заботится о том, имеет ли запрос права на доступ к запрашиваемому URL-адресу. Но некоторые из обработанных ранее фильтров могут остановить обработку цепочки фильтров, просто не вызывая FilterChain.doFilter(request, response);. Например, фильтр CSRF может остановить обработку цепи фильтра, если запрос не имеет параметра csrf.

Что делать, если я хочу защитить свой REST API с помощью JWT-токена, который извлекается из входа? Я должен настроить два http-тега конфигурации пространства имен, права? Другой для /login с UsernamePasswordAuthenticationFilter, а другой для URL REST, с пользовательским JwtAuthenticationFilter.

Нет, ты не вынужден так поступать. Вы можете объявить как UsernamePasswordAuthenticationFilter, так и JwtAuthenticationFilter в одном и том же элементе http, но это зависит от конкретного поведения каждого из этих фильтров. Оба подхода возможны, и какой из них выбрать в основном зависит от собственных предпочтений.

Выполняет ли настройка двух http-элементов два SpringSecurityFitlerChains?

Да, это правда

По умолчанию отключен UserPasswordAuthenticationFilter, пока я не объявляю форму-логин?

Да, вы могли видеть это в фильтрах, поднятых в каждой из конфигураций, которые я разместил

Как заменить SecurityContextPersistenceFilter на один, который получит аутентификацию из существующего JWT-токена, а не JSESSIONID?

Вы можете избежать SecurityContextPersistenceFilter, просто настроив стратегию сеанса в <http:element>. Просто настройте следующим образом:

<security:http create-session="stateless" >

Или, в этом случае вы можете перезаписать его другим фильтром, таким образом внутри элемента <security:http>:

<security:http ...>  
   <security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>    
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />

EDIT:

Один вопрос: "У вас также может быть несколько механизмов обработки аутентификации, настроенных в том же FilterchainProxy". Будет ли последний перезаписывать аутентификацию, выполняемую первым, если объявить несколько фильтров аутентификации (Spring)? Как это связано с наличием нескольких поставщиков проверки подлинности?

Это, в конечном счете, зависит от реализации каждого самого фильтра, но это правда, что последние фильтры аутентификации, по крайней мере, могут перезаписывать любую предыдущую аутентификацию, которая в конечном итоге была сделана предыдущими фильтрами.

Но это не обязательно произойдет. У меня есть некоторые производственные случаи в защищенных службах REST, где я использую своего рода токен авторизации, который может предоставляться как в заголовке Http, так и внутри тела запроса. Поэтому я настраиваю два фильтра, которые восстанавливают этот токен, в одном случае из заголовка Http, а другой - из тела запроса собственного запроса на отдых. Это правда, что если один HTTP-запрос предоставляет этот токен аутентификации как заголовок Http, так и внутри тела запроса, оба фильтра попытаются выполнить механизм аутентификации, делегирующий его диспетчеру, но его можно было бы легко избежать, просто проверив, является ли запрос уже аутентифицирован только в начале метода doFilter() для каждого фильтра.

Наличие нескольких фильтров аутентификации связано с наличием нескольких провайдеров аутентификации, но не принуждает их. В том случае, когда я ранее показывал, у меня есть два фильтра проверки подлинности, но у меня есть только один поставщик проверки подлинности, так как оба фильтра создают объект аутентификации того же типа, поэтому в обоих случаях диспетчер проверки подлинности делегирует его одному провайдеру.

И напротив, у меня тоже есть сценарий, в котором я публикую только один файл UsernamePasswordAuthenticationFilter, но учетные данные пользователя могут содержаться в БД или LDAP, поэтому у меня есть два поддерживающих провайдера UsernamePasswordAuthenticationToken, и AuthenticationManager делегирует любую попытку аутентификации из фильтра провайдерам для проверки достоверности учетных данных.

Итак, я думаю, ясно, что ни количество фильтров проверки подлинности не определяет количество поставщиков проверки подлинности, ни объем провайдера не определяют количество фильтров.

Кроме того, в документации указано, что SecurityContextPersistenceFilter отвечает за очистку SecurityContext, что важно для пула потоков. Если я опустил или предоставил пользовательскую реализацию, я должен выполнить очистку вручную, не так ли? При настройке цепочки больше похожих ошибок?

Я не внимательно смотрел на этот фильтр раньше, но после вашего последнего вопроса я проверял его реализацию и, как обычно, в Spring, почти все можно было настроить, расширить или перезаписать.

SecurityContextPersistenceFilter делегаты в SecurityContextRepository реализация поиска SecurityContext. По умолчанию используется HttpSessionSecurityContextRepository, но это может быть изменено с использованием одного из конструкторов фильтра. Поэтому может быть лучше написать SecurityContextRepository, который соответствует вашим потребностям и просто настроить его в SecurityContextPersistenceFilter, веря в это доказанное поведение, а не начинать делать все с нуля.

Ответ 2

UsernamePasswordAuthenticationFilter используется только для /login, а последние фильтры не являются?

Нет, UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter, и это содержит RequestMatcher, это означает, что вы можете определить свой собственный URL-адрес обработки, этот фильтр обрабатывает только тег RequestMatcher, соответствующий URL-адресу запроса, URL-адрес обработки по умолчанию /login.

Фильтры позже могут обрабатывать запрос, если UsernamePasswordAuthenticationFilter выполняет chain.doFilter(request, response);.

Подробнее о основных филерах

Элемент пространства имен формы-входа автоматически настраивает эти фильтры?

UsernamePasswordAuthenticationFilter создается <form-login>, это Стандартные псевдонимы фильтра и порядок

Поддерживает ли каждый запрос (аутентифицированный или нет) фильтр FilterSecurityInterceptor для URL-адреса, не входящего в систему?

Это зависит от того, успешны ли предыдущие филеры, но FilterSecurityInterceptor является последним филером.

Выполняет ли настройка двух http-элементов два SpringSecurityFitlerChains?

Да, каждый fitlerChain имеет RequestMatcher, если RequestMatcher соответствует запросу, запрос будет обрабатываться филерами в цепочке фидеров.

По умолчанию RequestMatcher соответствует всем запросам, если вы не настраиваете шаблон, или можете настроить конкретный URL-адрес (<http pattern="/rest/**").

Если вы хотите больше узнать о фитлетах, я думаю, вы можете проверить исходный код в spring безопасности. doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)