Ответ 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, веря в это доказанное поведение, а не начинать делать все с нуля.