Цепочка фильтров безопасности Spring - очень сложный и гибкий двигатель.
Ключевые фильтры в цепочке есть (в заказе)
- SecurityContextPersistenceFilter (восстанавливает аутентификацию из JSESSIONID)
- UsernamePasswordAuthenticationFilter (выполняет аутентификацию)
- ExceptionTranslationFilter (перехватывать исключения безопасности из FilterSecurityInterceptor)
- FilterSecurityInterceptor (может выдавать исключения аутентификации и авторизации)
Изучив текущую документацию стабильного выпуска 4.2.1 , раздел 13.3 Упорядочение фильтров, вы можете увидеть организацию фильтров всей цепочки фильтров:
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, который позволяет запускать сервисы запомнить меня, туда будет помещен подходящий запомненный объект аутентификации.
AnonymousAuthenticationFilter , так что если ранее механизм обработки аутентификации не обновлял SecurityContextHolder, туда будет помещен анонимный объект аутентификации
ExceptionTranslationFilter , для перехвата любых исключений Spring Security, чтобы можно было либо вернуть ответ об ошибке HTTP, либо запустить соответствующий AuthenticationEntryPoint.
FilterSecurityInterceptor , для защиты веб-URI и создания исключений, когда доступ запрещен
Теперь я постараюсь ответить на ваши вопросы один за другим:
Я запутался, как эти фильтры используются. Неужели для весны, предоставленной формы входа в систему, UsernamePasswordAuthenticationFilter используется только для / login, а последние фильтры - нет? Элемент пространства имен form-login автоматически настраивает эти фильтры? Каждый запрос (аутентифицированный или нет) достигает FilterSecurityInterceptor для URL, не входящего в систему?
Как только вы настраиваете <security-http>
раздел, для каждого из них вы должны как минимум предоставить один механизм аутентификации. Это должен быть один из фильтров, которые соответствуют группе 4 в разделе 13.3 «Порядок фильтров» из документации Spring Security, на которую я только что ссылался.
Это минимальный действительный элемент безопасности: 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 автоматически конфигурируются.
Фактически, должен быть настроен один механизм обработки аутентификации, и даже бины пространства имен безопасности обрабатывают заявки на это, выдавая ошибку во время запуска, но это можно обойти, добавив атрибут entry-point-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.
Итак, теперь вопросы:
Неужели для весны, предоставленной формы входа в систему, UsernamePasswordAuthenticationFilter используется только для / login, а последние фильтры - нет?
Да, он используется, чтобы попытаться завершить механизм обработки входа в систему, если запрос соответствует URL-адресу UsernamePasswordAuthenticationFilter. Этот URL-адрес может быть настроен или даже изменен в соответствии с каждым запросом.
Вы также можете иметь более одного механизма обработки аутентификации, настроенного в одном и том же FilterchainProxy (например, HttpBasic, CAS и т. Д.).
Элемент пространства имен form-login автоматически настраивает эти фильтры?
Нет, элемент form-login настраивает UsernamePasswordAUthenticationFilter, и в случае, если вы не предоставляете 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 конфигурации пространства имен, права? Другой для / логин с UsernamePasswordAuthenticationFilter
, и другой для REST URL, с обычаем JwtAuthenticationFilter
.
Нет, вы не обязаны делать это. Вы можете объявить оба UsernamePasswordAuthenticationFilter
и один и JwtAuthenticationFilter
тот же элемент http, но это зависит от конкретного поведения каждого из этих фильтров. Оба подхода возможны, и какой из них выбрать в конечном итоге зависит от собственных предпочтений.
Создает ли конфигурация двух элементов http два элемента springSecurityFitlerChains?
Да, это правда
Имя пользователяPasswordAuthenticationFilter отключено по умолчанию, пока я не объявлю форму входа в систему?
Да, вы могли видеть это в фильтрах, поднятых в каждом из конфигов, которые я выложил
Как заменить 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" />
РЕДАКТИРОВАТЬ:
Один вопрос о том, что «в одной и той же FilterchainProxy может быть настроено несколько механизмов обработки аутентификации». Будет ли последняя перезаписывать аутентификацию, выполняемую первой, если объявляет несколько фильтров (реализация Spring)? Как это связано с наличием нескольких поставщиков аутентификации?
Наконец, это зависит от реализации каждого фильтра, но верно то, что последние фильтры аутентификации, по крайней мере, способны перезаписывать любую предыдущую аутентификацию, в конечном итоге выполненную предыдущими фильтрами.
Но это не обязательно произойдет. У меня есть несколько производственных случаев в защищенных REST-сервисах, где я использую токен авторизации, который может быть предоставлен как в виде заголовка Http, так и внутри тела запроса. Поэтому я настраиваю два фильтра, которые восстанавливают этот токен, в одном случае из заголовка Http, а в другом - из тела запроса собственного запроса покоя. Это правда, что если один http-запрос предоставляет этот токен аутентификации как в виде заголовка Http, так и внутри тела запроса, оба фильтра будут пытаться выполнить механизм аутентификации, делегируя его менеджеру, но этого можно легко избежать, просто проверяя, является ли запрос уже аутентифицированы только в начале doFilter()
метода каждого фильтра.
Наличие более одного фильтра аутентификации связано с наличием более одного поставщика аутентификации, но не форсируйте его. В случае, который я описал ранее, у меня есть два фильтра аутентификации, но у меня есть только один поставщик аутентификации, так как оба фильтра создают объект аутентификации одного типа, поэтому в обоих случаях менеджер аутентификации делегирует его одному и тому же поставщику.
И напротив, у меня тоже есть сценарий, в котором я публикую только один UsernamePasswordAuthenticationFilter, но учетные данные пользователя могут содержаться в БД или LDAP, поэтому у меня есть два поддерживаемых провайдера UsernamePasswordAuthenticationToken, и AuthenticationManager делегирует любую попытку аутентификации от фильтра поставщикам последовательно проверять полномочия.
Поэтому я думаю, что ясно, что ни количество фильтров аутентификации не определяет количество поставщиков аутентификации, ни количество провайдеров не определяет количество фильтров.
Также в документации говорится, что SecurityContextPersistenceFilter отвечает за очистку SecurityContext, что важно из-за пула потоков. Если я пропущу это или предоставлю пользовательскую реализацию, я должен выполнить очистку вручную, верно? Есть ли больше подобных ошибок при настройке цепочки?
Раньше я не внимательно изучал этот фильтр, но после вашего последнего вопроса я проверял его реализацию, и, как обычно в Spring, почти все можно было настроить, расширить или переписать.
В SecurityContextPersistenceFilter делегаты в SecurityContextRepository реализации Поиски SecurityContext. По умолчанию используется HttpSessionSecurityContextRepository , но это можно изменить с помощью одного из конструкторов фильтра. Поэтому может быть лучше написать SecurityContextRepository, который соответствует вашим потребностям, и просто настроить его в SecurityContextPersistenceFilter, полагаясь на его проверенное поведение, а не начинать все с нуля.