В качестве примеров можно привести X.509, Siteminder и аутентификацию контейнером Java EE, в котором работает приложение. При использовании предварительной аутентификации Spring Security должен:
-
Идентифицировать пользователя, делающего запрос.
-
Получить полномочия для пользователя.
Детали будут зависеть от внешнего механизма аутентификации. Пользователь может быть идентифицирован по информации его сертификата в случае X.509, или по заголовку HTTP-запроса в случае Siteminder. Если полагаться на аутентификацию контейнера, пользователь будет идентифицирован вызовом метода getUserPrincipal()
по входящему HTTP-запросу. В некоторых случаях внешний механизм может предоставить информацию о роли/полномочиях пользователя, но в других случаях полномочия необходимо получать из отдельного источника, такого как UserDetailsService
.
Классы фреймворка предварительной аутентификации
Поскольку большинство механизмов предварительной аутентификации следуют одному и тому же шаблону, Spring Security содержит набор классов, которые обеспечивают внутренний фреймворк для реализации поставщиков предварительной аутентификации. Это позволяет устранить дублирование и добавлять новые реализации в структурированном виде, без необходимости писать все с нуля. Об этих классах не требуется ничего знать, если нужно использовать что-то наподобие аутентификации на основе X.509, так как она уже предусматривает вариант конфигурации пространства имен, который более прост в использовании и с которым проще начинать работать. Если необходимо использовать явную конфигурацию бинов или вы планируете написать собственную реализацию, то понимание того, как работают передаваемые реализации, будет полезным. Найти классы можно в org.springframework.security.web.authentication.preauth
. Здесь мы приведем лишь общее описание, поэтому вам следует обратиться к Javadoc и источникам, когда это потребуется.
AbstractPreAuthenticatedProcessingFilter
Этот класс проверяет текущее содержимое контекста безопасности и, если он пуст, пытается извлечь информацию о пользователе из HTTP-запроса и передать ее AuthenticationManager
. Подклассы переопределяют следующие методы для получения этой информации:
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
protected abstract fun getPreAuthenticatedPrincipal(request: HttpServletRequest): Any?
protected abstract fun getPreAuthenticatedCredentials(request: HttpServletRequest): Any?
После их вызова фильтр создаст PreAuthenticatedAuthenticationToken
, содержащий возвращаемые данные, и отправит его на аутентификацию. Под "аутентификацией" здесь мы на самом деле подразумеваем только дальнейшую обработку для возможной загрузки полномочий пользователя, но при соблюдении стандартной архитектуры аутентификации Spring Security.
Как и другие фильтры аутентификации Spring Security, фильтр предварительной аутентификации имеет свойство authenticationDetailsSource
, которое по умолчанию создает объект WebAuthenticationDetails
для хранения дополнительной информации, такой как идентификатор сессии и IP-адрес источника, в свойстве details
объекта Authentication
. В случаях, если информацию о роли пользователя можно получить из механизма предварительной аутентификации, эти данные также будут храниться в этом свойстве, с учетом сведений, реализующих интерфейс GrantedAuthoritiesContainer
. Это позволяет поставщику аутентификации считывать полномочия, которые были выделены пользователю извне. Далее мы рассмотрим конкретный пример.
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
Если фильтр сконфигурирован с учетом authenticationDetailsSource
, который является экземпляром этого класса, информация о полномочиях получается путем вызова метода isUserInRole(String role)
для каждой "отображаемой роли" из заранее определенного набора. Класс получает их от сконфигурированного MappableAttributesRetriever
. Возможные варианты реализации предусматривают жесткое кодирование списка в контексте приложения и считывание информации о роли из информации в разделе <security-role>
в файле web.xml
. В примере приложения для предварительной аутентификации используется последний подход.
Существует дополнительный этап, на котором роли (или атрибуты) отображаются на объекты GrantedAuthority
из Spring Security через сконфигурированный Attributes2GrantedAuthoritiesMapper
. По умолчанию к именам просто добавляется обычный префикс ROLE_
, но это дает полный контроль над логикой работы.
PreAuthenticatedAuthenticationProvider
Поставщику, прошедшему предварительную аутентификацию, остается лишь загрузить объект UserDetails
для пользователя. Он делает это путем делегирования полномочий сервису AuthenticationUserDetailsService
. Последний похож на стандартный UserDetailsService
, но принимает объект Authentication
, а не просто имя пользователя:
public interface AuthenticationUserDetailsService {
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
}
Этот интерфейс может иметь и другие применения, но при предварительной аутентификации он позволяет получить доступ к полномочиям, которые были упакованы в объект Authentication
, что можно было наблюдать в предыдущем разделе. Это осуществляет класс PreAuthenticatedGrantedAuthoritiesUserDetailsService
. В качестве альтернативы он может делегировать полномочия стандартному UserDetailsService
через реализацию UserDetailsByNameServiceWrapper
.
Http403ForbiddenEntryPoint
AuthenticationEntryPoint
отвечает за запуск процесса аутентификации для неаутентифицированного пользователя (если он пытается получить доступ к защищенному ресурсу), но в случае предварительной аутентификации не применяется. Вы можете сконфигурировать ExceptionTranslationFilter
с использованием экземпляра этого класса только в том случае, если не используете предварительную аутентификацию в сочетании с другими механизмами аутентификации. Его вызов произойдет, если пользователь будет отклонен AbstractPreAuthenticatedProcessingFilter
, что приведет к null-аутентификации. При его вызове всегда возвращается код ответа 403-forbidden
.
Конкретные реализации
Аутентификация на основе X.509 описана в отдельной главе. Здесь мы рассмотрим некоторые классы, которые обеспечивают поддержку других сценариев предварительной аутентификации.
Аутентификация по заголовку запроса (Siteminder)
Внешняя система аутентификации может передавать информацию приложению, устанавливая определенные заголовки в HTTP-запросе. Наглядным примером такой системы является Siteminder, который передает имя пользователя в заголовке SM_USER
. Этот механизм поддерживается классом RequestHeaderAuthenticationFilter
, который попросту извлекает имя пользователя из заголовка. По умолчанию в качестве имени заголовка используется имя SM_USER
. Более подробную информацию см. в javadoc
.
Обратите внимание, что при использовании подобной системы фреймворк вообще не выполняет никаких проверок подлинности, поэтому крайне важно, чтобы внешняя система была надлежащим образом сконфигурирована и обеспечивала защиту любого обращения к приложению. Если злоумышленник сможет подделать заголовки в исходном запросе таким образом, что это нельзя будет обнаружить, то он сможет выбрать любое имя пользователя по своему усмотрению.
Пример конфигурации с использованием Siteminder
Типичная конфигурация с использованием этого фильтра выглядит следующим образом:
<security:http>
<!-- Additional http configuration omitted -->
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
Здесь мы предположили, что пространство имен безопасности используется для конфигурации. Также предполагается, что UserDetailsService
был добавлен (под названием "userDetailsService") в конфигурацию, чтобы загрузить роли пользователя.
Аутентификация на уровне контейнера Java EE
Класс J2eePreAuthenticatedProcessingFilter
будет извлекать имя пользователя из свойства userPrincipal
для HttpServletRequest
. Использование этого фильтра обычно сочетается с использованием ролей Java EE, как описано выше в подразделе, посвященном J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.
В проекте с образцами есть образец приложения, в котором используется этот подход, поэтому возьмите код с GitHub и посмотрите на файл контекста приложения, если вам интересно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ