У цьому розділі обговорюється високорівнева архітектура Spring Security у додатках на базі сервлетів.

Короткий опис фільтрів

Підтримка сервлетів у Spring Security заснована на екземплярах Filter сервлетів, тому спочатку корисно розглянути роль Filter в цілому. На малюнку нижче показана типова рівнева структура обробників для одного HTTP-запиту.

Клієнт відправляє запит додатку, а контейнер створює FilterChainякий містить екземпляри Filter та Servlet, який повинен обробити HttpServletRequest на основі шляху URI запиту. У програмі Spring MVC Servlet є екземпляром DispatcherServlet. Тільки один Servlet може обробляти один HttpServletRequest та HttpServletResponse. Однак при цьому можна використовувати більше одного Filter для:

  • Запобігання виклику Filter або Servlet, які стоять нижче. У цьому випадку Filter зазвичай записує HttpServletResponse.

  • Зміни HttpServletRequest або HttpServletResponse , які використовуються наступними Filter та Servlet

Потужність Filter залежить від FilterChain, який передається до нього.

Приклад використання FilterChain
Java

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // робимо щось перед виконанням решти програми
        chain.doFilter(request, response) ;
        // invoke the rest of the application // робимо щось після виконання решти програми }
Kotlin

fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // робимо щось перед виконанням решти програми
    chain.doFilter(request, response)
    // invoke the rest of the application // робимо що-небудь після виконання решти програми }

Оскільки Filter впливає лише на Filter та Servlet, які стоять нижче, порядок виклику кожного Filter надзвичайно важливий.

DelegatingFilterProxy

Spring передбачає реалізацію Filter під назвою DelegatingFilterProxy, яка дозволяє встановити міст між життєвим циклом контейнера сервлетів та ApplicationContext зі Spring. Контейнер сервлетів дозволяє реєструвати екземпляри Filter, використовуючи власні стандарти, але йому невідомо про біни, визначені Spring. DelegatingFilterProxy можна зареєструвати через стандартні механізми контейнера сервлетів, але водночас делегувати всю роботу біну Spring, що реалізує Filter.

Тут показано, як DelegatingFilterProxy вписується в схему з екземплярами Filter та FilterChain.

Delegate здійснює пошук Bean Filter0 у ApplicationContext і потім викликає Bean Filter0. Псевдокод DelegatingFilterProxy показаний нижче.

Псевдокод DelegatingFilterProxy
Java
        
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // У відтермінованому режимі отримуємо фільтр, який був зареєстрований як бін Spring
    // Для прикладу в DelegatingFilterProxy делегат є екземпляром Bean Filter0
    Filter delegate = getFilterBean(someBeanName);
    // делегуємо роботу біну Spring delegate.doFilter(request, response);
}
Kotlin

fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
    // У відкладеному режимі отримуємо фільтр, який був зареєстрований як бін Spring
    // Для прикладу в DelegatingFilterProxy делегат є екземпляром Bean Filter0
    val delegate: Filter = getFilterBean(someBeanName)
    // делегуємо роботу біну Spring delegate.doFilter(request, response)
}

Ще однією перевагою DelegatingFilterProxy є те, що він дозволяє відкласти пошук екземплярів бінів Filter. Це важливо, оскільки контейнеру необхідно зареєструвати екземпляри Filter перед запуском контейнера. Однак Spring зазвичай використовує ContextLoaderListener для завантаження бінів Spring, що відбувається тільки після реєстрації екземплярів Filter.

FilterChainProxy

Засоби підтримки сервлетів Spring Security містяться в FilterChainProxy. FilterChainProxy — це спеціалізований Filter, що передбачається Spring Security, який дозволяє делегувати повноваження багатьом екземплярам Filter через <SecurityFilterChain. Оскільки FilterChainProxy є біном, він зазвичай обернутий у DelegatingFilterProxy.

SecurityFilterChain

SecurityFilterChain використовується FilterChainProxy для визначення того, які Filter із Spring Security необхідно викликати для цього запиту.

Фільтри безпеки в SecurityFilterChain зазвичай є бінами, але вони зареєстровані в FilterChainProxy замість DelegatingFilterProxy. FilterChainProxy передбачає низку переваг у порівнянні з реєстрацією безпосередньо в контейнері сервлетів або DelegatingFilterProxy. По-перше, він забезпечує відправну точку для всіх засобів підтримки сервлетів у Spring Security. З цієї причини, якщо ти намагатимешся усунути неполадки в засобах підтримки сервлетів Spring Security, почати найкраще буде з додавання точки налагодження в FilterChainProxy.

По-друге, оскільки FilterChainProxy займає центральне місце при використанні Spring Security, він може виконувати завдання, які не розглядаються як опціональні. Наприклад, він очищає SecurityContext, щоб уникнути витоку пам'яті. Він також застосовує HttpFirewall зі Spring Security для захисту додатків від певних типів атак.

Крім того, він забезпечує більшу гнучкість при визначенні моменту, коли необхідно викликати SecurityFilterChain. У контейнері сервлетів екземпляри Filter викликаються лише на основі URL-адреси. Однак FilterChainProxy може визначити виклик на основі чого завгодно HttpServletRequest, використовуючи інтерфейс RequestMatcher.

Фактично, FilterChainProxy можна використовувати для визначення того, який SecurityFilterChain слід використовувати. Це дозволяє забезпечити абсолютно окрему конфігурацію для різних фрагментів твого додатку. /cdn.javarush.com/images/article/5d90186d-8562-4c03-96bf-8300ce42fcf4/512.jpeg" alt="">

На малюнку з кількома SecurityFilterChain екземпляр FilterChain вирішує, яку SecurityFilterChain слід використовувати. Буде викликана лише перша SecurityFilterChain, що збігаєься. Якщо робиться запит на URL-адресу /api/messages/, вона спочатку буде зіставлена з шаблоном SecurityFilterChain0 через /api/**, тому буде викликано лише SecurityFilterChain0, навіть якщо він також буде відповідати SecurityFilterChainn. Якщо буде запит на URL-адресу /messages/, вона не буде зіставлена з шаблоном SecurityFilterChain0 через /api/**, тому FilterChainProxy продовжуватиме перебирати кожну SecurityFilterChain. Передбачається, що інші екземпляри SecurityFilterChain, що збігаються з SecurityFilterChainn, не будуть викликані.

Зверни увагу, що в SecurityFilterChain0 налаштовано лише три екземпляри Filter. Однак у SecurityFilterChainn налаштовані чотири Filter. Важливо, що кожна SecurityFilterChain може бути унікальною та конфігурованою ізольовано. Насправді SecurityFilterChain може мати нуль екземплярів Filter, якщо програмі потрібно, щоб Spring Security ігнорував певні запити.

Фільтри безпеки

Фільтри Spring Security додаються до FilterChainProxy через API для SecurityFilterChain. Порядок екземплярів Filter має значення. Зазвичай необхідності знати порядок проходження Filter у Spring Security немає. Тим не менш, у деяких випадках корисно знати, в якому порядку вони розташовані.

Нижче наведено повний упорядкований список фільтрів Spring Security:

  • ForceEagerSessionCreationFilter

  • ChannelProcessingFilter

  • WebAsyncManagerIntegrationFilter

  • SecurityContextPersistenceFilter

  • HeaderWriterFilter

  • CorsFilter

  • CsrfFilter

  • LogoutFilter

  • OAuth2AuthorizationRequestRedirectFilter

  • Saml2WebSsoAuthenticationRequestFilter

  • X509AuthenticationFilter

  • AbstractPreAuthenticatedProcessingFilter

  • CasAuthenticationFilter

  • OAuth2LoginAuthenticationFilter

  • Saml2WebSsoAuthenticationFilter

  • UsernamePasswordAuthenticationFilter

  • OpenIDAuthenticationFilter

  • DefaultLoginPageGeneratingFilter

  • DefaultLogoutPageGeneratingFilter

  • ConcurrentSessionFilter

  • DigestAuthenticationFilter

  • BearerTokenAuthenticationFilter

  • BasicAuthenticationFilter

  • RequestCacheAwareFilter

  • SecurityContextHolderAwareRequestFilter

  • JaasApiIntegrationFilter

  • RememberMeAuthenticationFilter

  • AnonymousAuthenticationFilter

  • OAuth2AuthorizationCodeGrantFilter

  • SessionManagementFilter

  • ExceptionTranslationFilter

  • FilterSecurityInterceptor

  • SwitchUserFilter

Обробка винятків безпеки

ExceptionTranslationFilter дозволяє перетворювати AccessDeniedException та AuthenticationException на HTTP-відповіді.

ExceptionTranslationFilter додається в FilterChainProxy в якість одного з фільтрів безпеки. /article/b93ab541-1f33-41c5-87db-87b46cfff1a6/512.jpeg" alt="">

  • 1. Спочатку ExceptionTranslationFilter звертається до FilterChain.doFilter(request, response), щоб викликати решту програми.

  • 2. Якщо користувач не автентифікований або виникає AuthenticationException, запусти процедуру аутентифікації.

    • TheSecurityContextHolder очищається

    • HttpServletRequest зберігається в RequestCache. Після того, як користувач успішно пройде аутентифікацію, RequestCache буде використаний для відтворення вихідного запиту.

    • AuthenticationEntryPoint використовується для облікових запитів даних у клієнта. Наприклад, він може перенаправити на сторінку входу в систему або відправити заголовок WWW-Authenticate.

  • 3. Інакше, якщо виникне AccessDeniedException, то в доступі буде відмовлено. AccessDeniedHandler викликається для обробки відмови у доступі.

Якщо програма не генерує AccessDeniedException або AuthenticationException, то ExceptionTranslationFilter нічого не робить.

Псевдокод для ExceptionTranslationFilter виглядає приблизно так:

Псевдокод ExceptionTranslationFilter
try {
    filterChain.doFilter(request, response); 
} catch (AccessDeniedException | AuthenticationException ex) {
    if (!authenticated || ex instanceof AuthenticationException) {
        startAuthentication();
    } else {
        accessDenied(); 
    }
}
  1. З короткого опису екземплярів Filter можна згадати, що виклик FilterChain.doFilter(request, response) еквівалентний виклику решти програми. Це означає, що якщо інша частина програми (наприклад, FilterSecurityInterceptor або метод безпеки) згенерує AuthenticationException або AccessDeniedException, то ці винятки будуть перехоплені та оброблені на цьому етапі.
  2. Якщо користувач не автентифікований або з'являється AuthenticationException, то запусти процедуру автентифікації.
  3. Інакше доступ буде відмовлено