В качестве примеров можно привести 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. Подклассы переопределяют следующие методы для получения этой информации:

Переопределяем AbstractPreAuthenticatedProcessingFilter
Java
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
Kotlin
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 и посмотрите на файл контекста приложения, если вам интересно.