Spring Security/Технический обзор Spring Security: различия между версиями

Материал из Викиучебника — открытых книг для открытого мира
Содержимое удалено Содержимое добавлено
Метки: с мобильного устройства из мобильной версии
Строка 172: Строка 172:


== ExceptionTranslationFilter ==
== ExceptionTranslationFilter ==
<code>ExceptionTranslationFilter</code> это фильтр Spring Security, который отвечает за обнаружение пробросов каких-либо исключений Spring Security. Такие исключения, как правило пробрасываются <code>AbstractSecurityInterceptor</code>, который является основным поставщиком сервисов авторизации. Мы будем обсуждать <code>AbstractSecurityInterceptor</code> в следующем разделе, а сейчас нам просто необходимо знать, что он производит Java исключения, и ничего не знает о HTTP или о том как проходит аутентификация принципала. Вместо него этот сервис предлагает <code>ExceptionTranslationFilter</code>, отвечая либо за возврат кода ошибки 403 (если принципал уже прошел проверку подлинности и следовательно просто не имеет достаточно прав доступа - как в шаге 7 описанном выше ), либо за запуск <code>AuthenticationEntryPoint</code> (если приниципал не прошел аутентификацию и мы должны начать шаг 3).
<code>ExceptionTranslationFilter</code> это фильтр Spring Security, который отвечает за обнаружение пробросов каких-либо исключений Spring Security. Такие исключения, как правило пробрасываются <code>AbstractSecurityInterceptor</code>, который является основным поставщиком сервисов авторизации. Мы будем обсуждать <code>AbstractSecurityInterceptor</code> в следующем разделе, а сейчас нам просто необходимо знать, что он производит Java исключения, и ничего не знает о HTTP или о том как проходит аутентификация принципала. Вместо него этот сервис предлагает <code>ExceptionTranslationFilter</code>, отвечая либо за возврат кода ошибки 403 (если принципал уже прошел проверку подлинности и следовательно просто не имеет достаточно прав доступа - как в шаге 7 описанном выше ), либо за запуск <code>AuthenticationEntryPoint</code> (если принципал не прошел аутентификацию и мы должны начать шаг 3).


== AuthenticationEntryPoint ==
== AuthenticationEntryPoint ==

Версия от 21:48, 25 ноября 2018

Эта статья представляет собой перевод Spring Security Reference Documentation, Ben Alex, Luke Taylor 3.0.2.RELEASE, Глава 5, Технический обзор.

Среда исполнения

Для Spring Security 3.0 требуется среда исполнения Java 5.0 или выше. Т.к. Spring Security стремится работать автономно, то нет никакой необходимости размещать специальные файлы-конфигурации в Java Runtime Environment. В частности, нет необходимости специально настраивать файл политики Java Authentication and Authorization Service (JAAS) или помещать Spring Security в общий CLASSPATH.

Аналогичным образом, если вы используете EJB контейнер или контейнер сервлета, то нет необходимости создавать где-либо специальные конфигурационные файлы или включать Spring Security в загрузчик классов сервера. Все необходимые файлы будут содержаться в вашем приложении.

Такая конструкция обеспечивает максимальную гибкость во время развертывания, так как вы можете просто скопировать целевой артефакт (JAR, WAR или EAR) из одной системы в другую, и он будет работать.

Ключевые компоненты

В Spring Security 3.0, содержимое jar-файла spring-security-core было урезано до минимума. Он не содержит никакого кода, связанного с безопасностью веб-приложений, LDAP или конфигурирования с помощью пространства имен. Здесь мы рассмотрим некоторые Java типы, которые можно найти в основном модуле. Они представляют собой строительные блоки каркаса. Так что если вам когда-нибудь понадобится выйти за рамки простой конфигурации пространства имен, то важно чтобы вы понимали что они из себя представляют, даже если вам не потребуется взаимодействовать с ними напрямую.

Объекты SecurityContextHolder, SecurityContext и Authentication

Самым фундаментальным явлется SecurityContextHolder. В нем мы храним информацию о текущем контексте безопасности приложения, который включает в себя подробную информацию о доверителе (принципале/пользователе) работающем в настоящее время с приложением. По умолчанию SecurityContextHolder использует ThreadLocal для хранения такой информации, что означает, что контекст безопасности всегда доступен для методов исполняющихся в том же самом потоке, даже если контекст безопасности явно не передается в качестве аргумента этих методов. Использование ThreadLocal таким образом, вполне безопасно, если принимаются меры для очистки потока после завершения обработки запроса текущего доверителя. Естественно Spring Security делает это за вас автоматически, поэтому нет необходимости беспокоиться об этом.

В некоторых приложениях использование ThreadLocal не является удачным решением, в силу их специфики работы с потоками. Например, клиент Swing может захотеть чтобы все потоки Java-машины использовали один и тот же контекст безопасности. SecurityContextHolder может быть настроен во время запуска с помощью специальной стратегии, чтобы вы могли определить как будет храниться контекст безопасности. Для одиночного приложения вы можете использовать стратегию SecurityContextHolder.MODE_GLOBAL. Другие приложения могут захотеть иметь потоки, порожденные от одного защищенного потока, предполагая наличие аналогичной безопасности. Это достигается путем использования SecurityContextHolder.MODE_INHERITABLETHREADLOCAL. Вы можете изменить режим по умолчанию SecurityContextHolder.MODE_THREADLOCAL двумя способами. Первый, установить соответствующее свойство системы, второй, вызвать статический метод класса SecurityContextHolder. Большинству приложений не требуется менять значение по умолчанию, в противном случае, чтобы узнать больше, изучите Javadocs для SecurityContextHolder.

Получение информации о текущем пользователе

Внутри SecurityContextHolder мы храним информацию о доверителе, взаимодействующим в настоящее время с приложением. Spring Security использует объект Authentication для представления этой информации. Как правило нет необходимости создавать объект Authentication самостоятельно, но запросы к объекту Authentication довольно распространенное действие. Вы можете использовать следующий код в любом месте вашего приложения, чтобы получить имя текущего зарегистрированного пользователя, например:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
  String username = ((UserDetails)principal).getUsername();
} else {
  String username = principal.toString();
}

Вызов getContext() возвращает экземпляр интерфейса SecurityContext. Это объект, который хранится в ThreadLocal. Как мы увидим ниже, большинство механизмов аутентификации в рамках Spring Security возвращают экземпляр UserDetails в качестве принципала.

Сервис UserDetails

Это еще один пункт на который стоит обратить внимание в приведенном выше фрагменте кода, в котором мы можем получить пользователя из объекта Authentication. "Пользователь" это просто Object. В большинстве случаев он может быть приведен к объекту UserDetails. UserDetails является одним из центральных интерфейсов Spring Security. Он представляет собой принципала, но в расширенном виде и с учетом специфики приложения. Думайте о UserDetails как об адаптере между вашей собственной БД пользователей и тем что требуется Spring Security внутри SecurityContextHolder. Будучи представлением чего-то из вашей БД пользователей, UserDetails довольно часто приводится к исходному объекту Вашего приложения, для того, чтобы вызвать бизнес-методы (такие как, getEmail(), getEmployeeNumber() и т. п.).

Возможно вы удивлены, когда я предоставил UserDetails объект? Как я это сделал? Я думаю, вы сказали, это было сделано декларативным способом и мне не потребовалось писать никакого Java кода — в этом дело? Короткий ответ заключается в том, что существует специальный интерфейс, называемый UserDetailsService. Единственный метод этого интерфейса принимает имя пользователя в виде String и возвращает UserDetails:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Это наиболее общий подход к загрузке информации о пользователе в Spring Security и вы увидите, что это используется в каркасе когда требуется информация о пользователе.

В случае успешной аутентификации, UserDetails используется для создания Authentication объекта, который хранится в SecurityContextHolder (подробнее об этом ниже). Хорошая новость заключается в том, что мы предлагаем целый ряд реализаций UserDetailsService, одна из них хранит данные в памяти (InMemoryDaoImpl), а другая использует JDBC (JdbcDaoImpl). Большинство пользователей, как правило пишут свои собственные реализации, поверх существующих объектов доступа к данным (DAO), который представляют их сотрудников, клиентов и других пользователей приложения. Помните то преимущество, что независимо от вашего UserDetailsService эти же данные можно получить из SecurityContextHolder с помощью приведенного выше фрагмента кода.

GrantedAuthority

Кроме получения "пользователя", еще одним важным методом, предоставляемым объектом Authentication, явлется getAuthorities(). Этот метод предоставляет массив объектов GrantedAuthority. Очевидно, что GrantedAuthority это полномочия, которые предоставляются пользователю. Такие полномочия (как правило называемые "роли"), как ROLE_ADMINISTRATOR или ROLE_HR_SUPERVISOR. Эти роли в дальнейшем настраиваются для веб-авторизации, авторизации методов и хранимых объектов. Другие части Spring Security способны интерпретировать эти полномочия, и ожидают их наличия. Объекты GrantedAuthority как правило загружаются с помощью UserDetailsService.

Обычно областью видимости для разрешений, выдаваемых объектами GrantedAuthority, является все приложение, а не отдельная предметная область. Таким образом, вы не можете иметь GrantedAuthority, который будет представлять собой разрешение для объекта Employee номер 54, потому что, если будут существовать тысячи таких полномочий, то память быстро кончится (или, по крайней мере, приведет к тому что идентификация пользователя будет занимать слишком много времени). Конечно Spring Security спроектирована так, чтобы удовлетворять самым широким потребностям, но для этих целей лучше воспользоваться специальными возможностями, которые предоставляются для обеспечения безопасности бизнес-объектов.

Резюме

Просто напомним, основными блоками Spring Security являются:

  • SecurityContextHolder, чтобы обеспечить доступ к SecurityContext.
  • SecurityContext, содержит объект Authentication и в случае необходимости информацию системы безопасности, связанную с запросом.
  • Authentication представляет принципала (пользователя авторизованной сессии) с точки зрения Spring Security.
  • GrantedAuthority отражает разрешения выданные доверителю в масштабе всего приложения.
  • UserDetails предоставляет необходимую информацию для построения объекта Authentication из DAO объектов приложения или других источника данных системы безопасности.
  • UserDetailsService, чтобы создать UserDetails, когда передано имя пользователя в виде String (или идентификатор сертификата или что-то подобное).

Теперь, когда у вас есть понимание многократно используемых компонентов, давайте внимательнее рассмотрим на процесс аутентификации.

Аутентификация

Spring Security можно использовать в самых разных средах аутентификации. Хотя мы рекомендуем использовать Spring Security для аутентификации, а не интегрировать ее с существующей Container Managed Authentication, хотя этот механизм и поддерживается. То же касается и интеграции с вашей собственной системой аутентификации.

Что представляет собой аутентификация в Spring Security

Рассмотрим стандартный сценарий аутентификации с которым все хорошо знакомы.

  1. Пользователю будет предложено войти в систему с именем пользователя и паролем.
  2. Система (успешно) подтверждает, что введен правильный пароль для данного имени пользователя.
  3. Получается контекстная информация для данного пользователя (список ролей и т. д.).
  4. Для пользователя устанавливается контекст безопасности.

Пользователь перенаправляется на выполнение операций, которые могут быть защищены механизмом контроля доступа, который проверяет необходимые разрешения для выполнения операций на основании информации текущего контекста безопасности.

Первые три пункта составляют непосредственно процесс аутентификации, поэтому мы рассмотрим, как это происходит в Spring Security.

  1. Получаются имя пользователя и пароль и объединяются в экземпляр класса UsernamePasswordAuthenticationToken (экземпляр интерфейса Authentication, который мы видели ранее).
  2. Токен передается экземпляру AuthenticationManager для проверки.
  3. AuthenticationManager возвращает полностью заполненный экземпляр Authentication в случае успешной аутентификации.
  4. Устанавливается контекст безопасности путем вызова SecurityContextHolder.getContext().setAuthentication(...), куда передается вернувшийся экземпляр Authentication.

С этого момента пользователь считается подлинным. Давайте рассмотрим код в качестве примера.

import org.springframework.security.authentication.*;
import org.springframework.security.core.*;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.context.SecurityContextHolder;

public class AuthenticationExample {
  private static AuthenticationManager am = new SampleAuthenticationManager();

  public static void main(String[] args) throws Exception {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    while(true) {
      System.out.println("Please enter your username:");
      String name = in.readLine();
      System.out.println("Please enter your password:");
      String password = in.readLine();
      try {
        Authentication request = new UsernamePasswordAuthenticationToken(name, password);
        Authentication result = am.authenticate(request);
        SecurityContextHolder.getContext().setAuthentication(result);
        break;
      } catch(AuthenticationException e) {
        System.out.println("Authentication failed: " + e.getMessage());
      }
    }
    System.out.println("Successfully authenticated. Security context contains: " +
              SecurityContextHolder.getContext().getAuthentication());
  }
}

class SampleAuthenticationManager implements AuthenticationManager {
  static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();

  static {
    AUTHORITIES.add(new GrantedAuthorityImpl("ROLE_USER"));
  }

  public Authentication authenticate(Authentication auth) throws AuthenticationException {
    if (auth.getName().equals(auth.getCredentials())) {
      return new UsernamePasswordAuthenticationToken(auth.getName(),
        auth.getCredentials(), AUTHORITIES);
      }
      throw new BadCredentialsException("Bad Credentials");
  }
}

Здесь мы написали маленькую программу, которая предлагает пользователю ввести имя и пароль, и выполняет выше описанные действия. AuthenticationManager, которые мы реализовали здесь, идентифицирует любого пользователя у которого имя совпадает с паролем. Для каждого пользователя назначается одна роль. Выше приведенный код выведет на экран нечто подобное:

Please enter your username:
bob
Please enter your password:
password
Authentication failed: Bad Credentials
Please enter your username:
bob
Please enter your password:
bob
Successfully authenticated. Security context contains: \
 org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230: \
 Principal: bob; Password: [PROTECTED]; \
 Authenticated: true; Details: null; \
 Granted Authorities: ROLE_USER

Обратите внимание, что обычно не нужно писать код, подобный этому. Обычно этот процесс происходит где-то внутри, например в фильтре веб аутентификации. Мы включили этот код, только для того, чтобы показать, что вопрос что на самом деле представляет собой аутентификация в Spring Security имеет довольно простой ответ. Пользователь считается идентифицированным (подтвержденным) когда SecurityContextHolder содержит полностью заполненный объект Authentication.

Установка содержимого SecurityContextHolder напрямую

На самом деле Spring Security не задумывается о том, каким образом объект Authentication попадет внутрь SecurityContextHolder. Существует только одно критическое требование, оно состоит в том что SecurityContextHolder должен содержать в себе объект Authentication, который представляет принципала до того момента как AbstractSecurityInterceptor (чуть позже, мы рассмотрим его подробней) потребуется авторизовать пользовательскую операцию.

Вы можете (и многие пользователи так и делают) написать собственные фильтры или MVC контроллеры для обеспечения взаимодействия с системами аутентификации, которые не основаны на Spring Security. Например, вы могли бы использовать Container-Managed аутентификацию, которая получает текущего пользователя из ThreadLocal или JNDI. Или вы можете работать на компанию, которая имеет собственную унаследованную систему аутентификации, которая является корпоративным "стандартом" и над которой у вас мало контроля. В таких ситуациях очень легко «запрячь» Spring Security в работу, и использовать старую систему аутентификации. Все что нужно сделать, это написать фильтр (или его эквивалент), который считывает информацию о пользователях из нужного места, строить Spring Security объект Authentication и помещает его в SecurityContextHolder.

Если вам интересно, как в реальности реализован менеджер AuthenticationManager, то мы рассмотрим его в главе «Ключевые сервисы».

Аутентификация в Веб-приложениях

Теперь давайте исследуем ситуацию, где вы используете Spring Security в веб-приложении (система безопасности в web.xml выключена). Как аутентифицируется пользователь и устанавливается контекст безопасности приложения?

Рассмотрим типичный процесс аутентификации веб-приложения

  1. Вы заходите на главную страницу и нажимаете на ссылку.
  2. Запрос идет на сервер и сервер решает что вы запрашиваете защищенный ресурс.
  3. Т.к. Ваша подлинность в настоящее время не подтверждена, то сервер отправляет ответ о том, что вы должны пройти процедуру аутентификации. Ответом будет либо код HTTP, либо перенаправление на особую веб-страницу.
  4. В зависимости от механизма аутентификации, браузер либо перенаправит вас на специальную веб-страницу, где можно будет заполнить форму, либо браузер каким-то другим образом получит данные подтверждающие подлинность Вашей личности (через базовый диалог аутентификации, куки, сертификат X.509 и т.п.).
  5. Браузер отошлет ответ на сервер. Это будет либо HTTP POST, с содержимым формы, которую вы заполнили, либо HTTP заголовок, содержащий ваши данные аутентификации.
  6. Далее сервер будет решать, является ли «удостоверение личности» действительными. Если оно действительно, то будет выполнен следующий шаг. Если же оно недействительно, то как правило браузер предложит попробовать идентифицироваться еще раз (т.е. вы вернетесь к шагу 2).
  7. Оригинальный запрос, который привел к запуску процедуры аутентификации, будет повторен. Надеемся, что вы имеете достаточно полномочий для доступа к защищенному ресурсу. Если вы имеете соответствующие права доступа, то запрос будет успешным. В противном случае вам будет возвращен код ошибки 403, что означает "запрещено".

Spring Security имеет явно реализованные классы отвечающие за большинство шагов, описанных выше. Основные участники (в порядке их использования) ExceptionTranslationFilter, AuthenticationEntryPoint и "механизм аутентификации", который отвечает за вызов AuthenticationManager который мы видели в предыдущем разделе.

ExceptionTranslationFilter

ExceptionTranslationFilter это фильтр Spring Security, который отвечает за обнаружение пробросов каких-либо исключений Spring Security. Такие исключения, как правило пробрасываются AbstractSecurityInterceptor, который является основным поставщиком сервисов авторизации. Мы будем обсуждать AbstractSecurityInterceptor в следующем разделе, а сейчас нам просто необходимо знать, что он производит Java исключения, и ничего не знает о HTTP или о том как проходит аутентификация принципала. Вместо него этот сервис предлагает ExceptionTranslationFilter, отвечая либо за возврат кода ошибки 403 (если принципал уже прошел проверку подлинности и следовательно просто не имеет достаточно прав доступа - как в шаге 7 описанном выше ), либо за запуск AuthenticationEntryPoint (если принципал не прошел аутентификацию и мы должны начать шаг 3).

AuthenticationEntryPoint

AuthenticationEntryPoint отвечает за шаг №3 из приведенного выше списка. Как вы понимаете, каждое веб-приложение имеет стратегию аутентификации по умолчанию (она может быть настроена как и почти все остальное в Spring Security, но в целях простоты пропустим это в настоящий момент). Все основные системы аутентификации будут иметь свою собственную реализацию AuthenticationEntryPoint, которая обычно выполняет одно из действий, описанных в шаге 3

Механизм аутентификации

Как только браузер отправит удостоверение подлинности вашей личности (POST запрос HTTP формы, либо HTTP заголовок) на сервере должно присутствовать нечто, что будет "собирать" эти данные аутентификации. Т.е. сейчас мы говорим о шаге №6 из нашего списка. В Spring Security есть специальное название для функции сбора аутентификационных данных от пользовательского агента (обычно это веб-браузер). Обычно ее называют "механизм аутентификации". Например, аутентификация на основе формы, либо базовая аутентификация. Сразу как только данные аутентификации будут собраны от пользовательского агента, для нашего «запроса» будет построен объект Authentication и предоставлен AuthenticationManager.

Если после этого механизм аутентификации получит обратно полностью заполненный объект Authentication, он будет считать запрос корректным и поместит объект Authentication в SecurityContextHolder, и повторно вызовет первоначального запрос (шаг 7). Если же AuthenticationManager отклонит запрос, то механизм аутентификации повторно запросит данные у пользовательского агента (шаг 2).

Сохранение SecurityContext между запросами

В зависимости от типа приложения может потребоваться стратегия хранения контекста безопасности между операциями пользователя. В типичном веб-приложении, пользователь регистрируется один раз и впоследствии идентифицируется по Id сессии. Сервер кэширует информацию принципала в течение сессии. В Spring Security, ответственность за хранение SecurityContext между запросами ложится на SecurityContextPersistenceFilter, который по умолчанию хранит контекст как атрибут HttpSession между запросами HTTP. Он восстанавливает контекст в SecurityContextHolder для каждого запроса и, что самое главное, очищает SecurityContextHolder после завершения запроса. Вы не должны напрямую взаимодействовать с HttpSession для решения задач безопасности. Для этого нет никаких оснований, всегда вместо этого используйте SecurityContextHolder.

Многие другие типы приложений(например, RESTful веб-сервисы без сохранения состояния) не используют HTTP сессии и будут требовать аутентификации при каждом запросе. Тем не менее, все равно очень важно, чтобы SecurityContextPersistenceFilter входил в цепочку, чтобы SecurityContextHolder очищался после каждого запроса.

Примечание В приложение, которое получает конкурентные запросы в одной сессии, один и тот же экземпляр SecurityContext будет разделяться между потоками. Несмотря на то, что используется ThreadLocal, это будет один и тот же экземпляр, что извлекается из HttpSession для каждого потока. Это будет иметь значение если вы захотите временно изменить контекст в выполняющемся потоке. Если Вы просто используете SecurityContextHolder.getContext().SetAuthentication(anAuthentication), то объект Authentication изменится во всех параллельных потоках, которые разделяют один и тот же экземпляр SecurityContext. Вы можете настроить поведение SecurityContextPersistenceFilter чтобы он создавал совершенно новый экземпляр SecurityContext для каждого запроса, чтобы один поток не влиял на другой. Альтернативный вариант, это создать новый экземпляр в том месте где требуется временно изменить контекст. Метод SecurityContextHolder.createEmptyContext() всегда возвращает новый экземпляр контекста.

Управление доступом (Авторизация) в Spring Security

В Spring Security основным интерфейсом, отвечающим за принятие решений в области контроля доступа, является AccessDecisionManager. У него имеется решающий метод, который принимает объект Authentication, представляющий принципала запрашивающего доступ, "объект безопасности" (см. ниже), а также список атрибутов метаданных безопасности, которые применяются к объекту (например, список ролей которым разрешен доступ).

Безопасность и Советы AOP

Если вы хорошо знакомы с AOP, то должны знать что существуют различные виды советов: before, after, throws и around. Совет around очень полезен, потому что советчик может выбирать, следует или нет осуществить вызов метода, следует или нет изменить отклик, следует или нет пробросить исключение. Spring Security предоставляет around советы как для вызова методов, так и для веб-запросов. Для вызова методов совет around реализуется с помощью стандартного AOP модуля Spring'а, а для веб-запросов с помощью стандартного фильтра.

Для тех, кто не знаком с AOP, главное понять, что Spring Security может защищать вызовы методов так же хорошо, как и веб-запросы. Для большинства людей важно обеспечить безопасность вызова методов на уровне сервисов. Потому что на уровне сервисов сосредоточено большинство бизнес-логики нынешнего поколения J2EE приложений. Если вам просто нужно обеспечить безопасность при вызове методов на уровне сервисов, то стандартный Spring AOP будет весьма уместным. Если вам нужно обеспечить безопасность непосредственно объектов предметной области, то вероятно следует рассмотреть вариант использования AspectJ.

Вы можете выбрать метод авторизации с использованием AspectJ или Spring AOP, или вы можете выбрать авторизацию веб-запросов с помощью фильтров. Вы можете использовать ноль, один, два или даже все три эти подхода вместе. Типичный шаблон использования выглядит следующим образом: использование авторизации для части веб-запросов, в сочетании авторизаций вызова методов уровня сервисов с помощью Spring AOP .

Защищенные Объекты и AbstractSecurityInterceptor

Что такое "защищенный объект" в общем смысле? Spring Security использует этот термин для обозначения любого объекта, к которому могут применяться механизмы обеспечения безопасности (например, авторизация). Наиболее распространенными примерами являются вызовы методов и веб-запросы.

Каждый поддерживамый защищенный объект имеет свой собственный класс перехватчик, который является подклассом AbstractSecurityInterceptor. Важно отметить, что в тот момент, когда вызывается AbstractSecurityInterceptor, SecurityContextHolder будет содержать корректный объект Authentication, если принципал прошел аутентификацию.

AbstractSecurityInterceptor обеспечивает последовательный рабочий процесс для обработки запросов к защищенному объекту, обычно:

  1. Поиск "конфигурационных атрибутов", связанных с текущим запросом
  2. Отправка защищенного объекта, текущего Authentication объекта и конфигурационных атрибутов объекту AccessDecisionManager для принятия решение об авторизации
  3. Опциональное изменение Authentication под которым происходит вызов
  4. Разрешение выполнения вызова безопасного объекта (при условии что доступ был разрешен)
  5. При наличии в конфигурации, вызов AfterInvocationManager, сразу после возврата из вызова защищенного объекта.

Что такое конфигурационные атрибуты

"Конфигурационный атрибут" можно рассматривать как String, которая имеет особое значение для классов, используемых AbstractSecurityInterceptor. Они представляются интерфейсом ConfigAttribute каркаса Spring Security. Они могут быть простыми именами ролей или иметь более сложный смысл, в зависимости от возможностей реализации AccessDecisionManager. AbstractSecurityInterceptor настроен так, чтобы использовать SecurityMetadataSource, который он использует для поиска атрибутов защищенного объекта. Обычно эта конфигурация будет скрыта от пользователя. Конфигурационные атрибуты устанавливаются в виде аннотаций для защищенных методов или атрибутов доступа для защищенных URL. Например, когда мы видим что-то вроде <intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/> в конфигурационном файле, это говорит о том, что конфигурационные атрибуты ROLE_A и ROLE_B должны применяться к веб-запросам, соответствующим заданному шаблону. На практике, с конфигурацией по умолчанию для AccessDecisionManager, это будет означает, что доступ получит каждый, кто имеет GrantedAuthority совпадающий с одним из этих двух атрибутов. Строго говоря, это просто атрибуты и их интерпретация зависит от реализации AccessDecisionManager. Префикс ROLE_ используется как маркер, чтобы показать что эти атрибуты обозначают роли пользователей и будут использованы классом RoleVoter каркаса Spring Security. Это имеет смысл только тогда, когда используется AccessDecisionManager основанный на voter. В следующей главе мы увидим как реализован AccessDecisionManager.

RunAsManager

Предположим, что AccessDecisionManager выдает разрешение на выполнение какому-либо запросу, тогда AbstractSecurityInterceptor, как правило, просто передаст данный запрос на обработку. Надо сказать, что в редких случаях пользователи могут захотеть заменить один Authentication объект в SecurityContext на другой Authentication объект, который будет обработан AccessDecisionManager с помощью вызова RunAsManager. Это может оказаться полезным в некоторых нетиповых ситуациях, например, если методу уровня сервисов нужно вызвать удаленную систему и идентифицироваться в ней с другими данными. Так как Spring Security автоматически распространяет аутентификационные данные с одного сервера на другой (предполагается, что вы пользуетесь правильно настроенными RMI или HttpInvoker клиентами протокола удаленного вызова), то эта возможность может быть полезной.

AfterInvocationManager

После того как безопасный объект отработал и произошел возврат из него - что может означать завершение вызова метода или работы цепочки фильтров - AbstractSecurityInterceptor получает последний шанс для обработки вызова. На данном этапе AbstractSecurityInterceptor может быть заинтересован в возможности изменить возвращаемый объект. Мы можем захотеть чтобы это произошло в том случае, если решение об авторизации не могло быть принято «на пути к» вызову безопасного объекта. Будучи полностью настраиваемым с помощью плагинов, AbstractSecurityInterceptor передаст управление AfterInvocationManager чтобы изменить возвращаемый объект, если это потребуется. Этот класс может даже полностью заменить возвращаемый объект, или выбросить исключение, или же вообще ничего не изменять, в зависимости от потребностей.

AbstractSecurityInterceptor и связанные с ним объекты показаны на рисунке 5.1, «Модель перехватчиков системы безопасности и «защищенного объекта».

Расширение модели защищенного объекта

Только те разработчики, которые рассматривают возможность создания полностью нового способа перехвата и авторизации запросов, должны напрямую использовать защищенные объекты. Например, можно было бы построить новый защищенный объект для системы обмена сообщениями. Все что требует обеспечения безопасности и обеспечивает способ перехвата вызовов (на подобие семантики AOP совета around) может быть превращено в защищенный объект. Можно сказать, что в большинстве Spring-приложений можно легко и прозрачно использовать три, в настоящее время поддержаващихся, типа защищенных объектов (MethodInvocation AOP альянса, AspectJ JoinPoint и FilterInvocation для веб-запросов).

Локализация

Spring Security поддерживает локализацию сообщений в исключениях, которые скорее всего увидят конечные пользователи. Если ваше приложение разработано для Англоговорящих пользователей, то по умолчанию вы не должны ничего делать, все сообщения Spring Security написаны на английском языке. Если вы должны поддерживать локализацию для других языков, то все что вам нужно знать, содержится в этом разделе.

Все сообщения исключений могут быть локализованы, включая сообщения, связанные с отказом аутентификации и запрета доступа (отказы авторизации). Исключения и логи, которые предназначены для разработчиков или лиц ответсвенных за развертывание приложения (включая некорректные атрибуты, нарушения контракта интерфейса, использование некорректных конструкторов, проверки во время запуска, логгирование режима отладки) и т.д, не локализуются, а вместо этого явно написаны в коде Spring Security на английском языке.

В поставляемом файле spring-security-core-xx.jar, вы найдете пакет org.springframework.security, который в свою очередь содержит файл messages.properties. На него должна быть установлена ссылка в вашем ApplicationContext, поскольку классы Spring Security реализуют Spring интерфейс MessageSourceAware и ожидают, что в преобразователь сообщений (message resolver) будет включена зависимость из контекста приложения в момент его запуска.. Обычно все, что Вы должны сделать, это зарегистрировать bean в контексте приложения, который будет ссылаться на сообщения. Ниже приведен пример:

<bean id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  <property name="basename" value="org/springframework/security/messages"/>
</bean>

Messages.properties назван в соответствии со стандартом пакетов ресурсов и содержит сообщения на языке по умолчанию, поддерживаемые Spring Security. Этот файл по умолчанию на английском языке. Если вы не зарегистрировали источник сообщений, Spring Security все равно будет работать правильно, и показывать английскую версию сообщений жестко заданных в коде.

Если вы хотите настроить файл messages.properties или поддерживать другие языки, то вы должны скопировать файл, переименовать в соответствии с правилами и зарегистрировать его в приведенном выше определении компонента. Файл содержит небольшое количество ключей сообщений, поэтому локализация не рассматриваться как основная инициатива. Если вы выполните локализацию этого файла, пожалуйста подумайте о том, чтобы поделиться вашей работой с сообществом. Для этого войдите в JIRA, создайте задачу и прикрепите к ней вашу версию переименованного и локализованного файла messages.properties.

В завершении нашего обсуждения локализации: Существует Spring ThreadLocal известеный как org.springframework.context.i18n.LocaleContextHolder. Вы должны установить LocaleContextHolder так, чтобы он представлял предпочтительную Locale каждого пользователя. Spring Security будет пытаться найти сообщения в соответствии с настройками Locale, который берется из ThreadLocal. Пожалуйста, обратитесь к документации Spring Framework для более подробной информациию об использовании LocaleContextHolder.