Ланцюжок фільтрів Spring 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 та збільшення винятків, коли доступ заборонено
Тепер я спробую продовжити ваші запитання по одному:
Мене бентежить, як ці фільтри використовуються. Хіба що для весни, наданої формою входу, ім'я користувачаPasswordAuthenticationFilter використовується лише для / входу, а останні фільтри - ні? Чи автоматично конфігурує ці фільтри елемент простору імен форм-форм? Чи кожний запит (автентифікований чи ні) доходить до 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.
Насправді має бути налаштований один механізм обробки автентифікації, і навіть захист бобів простору імен обробляє претензії на це, видаючи помилку під час запуску, але це можна обійти, додавши атрибут введення точки-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.
Отже, тепер питання:
Хіба що для весни, наданої формою входу, ім'я користувачаPasswordAuthenticationFilter використовується лише для / входу, а останні фільтри - ні?
Так, він використовується для спроби завершити механізм обробки входу у випадку, якщо запит відповідає URL-адресі UsernamePasswordAuthenticationFilter. Цей URL-адрес можна налаштувати або навіть змінити його поведінку, щоб відповідати кожному запиту.
Ви також можете мати більше ніж один механізм обробки автентифікації, налаштований у тому ж FilterchainProxy (наприклад, HttpBasic, CAS тощо).
Чи автоматично конфігурує ці фільтри елемент простору імен форм-форм?
Ні, елемент форми входу у форму налаштовує 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 тегів http, права? Інший для / входу в систему UsernamePasswordAuthenticationFilter
, а інший для URL-адреси REST, за допомогою власного користування JwtAuthenticationFilter
.
Ні, ви не змушені це робити. Ви можете оголосити UsernamePasswordAuthenticationFilter
і той JwtAuthenticationFilter
і інший елемент http, але це залежить від конкретної поведінки кожного з цих фільтрів. Можливі обидва підходи, а який вибрати остаточно, залежить від власних уподобань.
Чи конфігурування двох елементів http створює два springSecurityFitlerChains?
Так, це правда
Чи вимкнено UsernamePasswordAuthenticationFilter за замовчуванням, поки я не оголошу форму входу?
Так, ви могли бачити це у фільтрах, піднятих у кожному з конфігурацій, які я опублікував
Як замінити 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 Spring)? Як це стосується наявності декількох постачальників аутентифікації?
Це, нарешті, залежить від самої реалізації кожного фільтра, але це правда, що останні фільтри аутентифікації принаймні здатні замінити будь-яку попередню аутентифікацію, зрештою зроблену попередніми фільтрами.
Але це не обов'язково відбудеться. У мене є кілька виробничих випадків у захищених службах REST, де я використовую своєрідний маркер авторизації, який може надаватися як у заголовку Http, так і всередині органу запиту. Отже, я налаштовую два фільтри, які відновлюють цей маркер, в одному випадку з заголовка Http, а в іншому - від органу запиту власного запиту відпочинку. Це правда, що якщо один http-запит надає маркер автентифікації як у заголовку Http, так і всередині органу запиту, обидва фільтри намагатимуться виконати механізм аутентифікації, передавши його менеджеру, але його можна легко уникнути, просто перевіривши, чи запит є вже автентифіковано лише на початку doFilter()
методу кожного фільтра.
Наявність декількох фільтрів аутентифікації пов'язана з наявністю більше ніж одного постачальника аутентифікації, але не змушуйте його. У випадку, який я викривав раніше, у мене є два фільтри аутентифікації, але у мене є лише один постачальник аутентифікації, оскільки обидва фільтри створюють один і той же тип об'єкта аутентифікації, тому в обох випадках менеджер аутентифікації делегує його тому ж постачальнику.
І навпаки цього, у мене теж є сценарій, коли я публікую лише одне ім'я користувачаPasswordAuthenticationFilter, але облікові дані користувачів обидва можуть міститися в БД або LDAP, тому у мене є два підтримуючих провайдера UsernamePasswordAuthenticationToken, а AuthenticationManager делегує будь-яку спробу аутентифікації з фільтра провайдерам послідовно підтверджувати облікові дані.
Отже, я думаю, що зрозуміло, що ні кількість фільтрів аутентифікації не визначає кількість постачальників аутентифікації, ні кількість провайдерів, не визначають кількість фільтрів.
Крім того, у документації зазначено SecurityContextPersistenceFilter, який відповідає за очищення SecurityContext, що важливо через об'єднання потоків. Якщо я пропускаю це або надаю користувальницьку реалізацію, я повинен здійснити очищення вручну, правда? Чи є більше подібних готчей при налаштуванні ланцюга?
Раніше я не ретельно заглядав у цей фільтр, але після вашого останнього питання я перевіряв його реалізацію, і, як правило, навесні, майже все можна було налаштувати, розширити чи перезаписати.
У SecurityContextPersistenceFilter делегати в SecurityContextRepository реалізації Пошуки SecurityContext. За замовчуванням використовується HttpSessionSecurityContextRepository , але це можна змінити за допомогою одного з конструкторів фільтра. Тому може бути краще написати сховище SecurityContextRepository, яке відповідає вашим потребам та просто налаштувати його в SecurityContextPersistenceFilter, довіряючи його перевіреній поведінці, а не починати робити все з нуля.