SPRING-SOURCE.RU
Исходный код:
Настраиваем базу данных
(Начинаем работать с базами данных)
Смена пароля и БД
(Смена пароля с использованием базы данных)
Детальная настройка JdbcDaoImpl
(Другой подход работы с базой данных. Этот пример не рабочий, он лишь отражает пример описанный в третьей части. Чтобы все работало вы должны подставить свои SQL данные)
Последнее обновление 18.08.2010

Детальный контроль доступа

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

В этой главе сначала мы посмотрим на технику планирования безопасности приложения и назначение user/group. После этого, мы рассмотрим два пути реализации детального контроля доступа - авторизация, которая может влиять на части страницы приложения. Далее, мы взглянем на подход Spring Security для защиты бизнес-уровня через аннотации методов и использования аспектно-ориентированного программирования. В конце, мы рассмотрим интересную способность защиты основанной на аннотациях, которая позволяет, на основе ролей, фильтровать коллекции данных.

В ходе этой части, мы:

Переосмысление функциональности и безопасности приложения

В данный момент, мы хотели бы вернуться к модели авторизации и потока JBCP приложения. Мы чувствуем, что у нас используется высокий уровень безопасности, но поток приложения не особенно подходит для публичного коммерческого сайта.

Планирование безопасности приложения

Планирование ролей для пользователя

Для нашего сайта мы будем использовать связывание классов пользователей с ролями, как представлено в таблице ниже (Spring Security GrantedAuthority значения).

Класс пользователя Описание Роли
Guest Пользователь, который не аутентифицирован и не remember me None (анонимный)
Consumer/ Customer Пользователь, имеющий учетную запись и имеет или не имеет покупки на сайте ROLE_CUSTOMER
ROLE_USER
Customer w/ Completed Purchase Пользователь, который имеет хотя бы одну покупку на сайте ROLE_PURCHASER
ROLE_USER
Administrator Администратор ответственный за управление пользователями итп ROLE_ADMIN
ROLE_USER
Supplier Поставщик продукта позволяет управлять инвентарем ROLE_SUPPLIER
ROLE_USER

Планирование безопасности на уровне страниц

Следующий уровень детализации планирования безопасности - это безопасность вокруг элементов уровня страницы. Оба этапа планирования может быть выполнено при помощи программ типа Visio, вы сами выбираете инструменты.

Методы детального контроля доступа

В отличие крупного контроля доступа, который мы обсуждали в предыдущих частях, детальный контроль доступа относится к отдельным частям страницы и не ограничивает доступ ко всей странице целиком.

Spring Security предоставляет два метода для выборочного отображения:

Давайте посмотрим как выглядит каждый из представленных подходов.

Мы хотим быть уверенными, что ссылки Log Out и My Orders, расположенные в меню, видимы только для пользователей, которые зашли в систему или имеют покупки (ROLE_USER и ROLE_CUSTOMER, соответственно). Также мы хотим, чтобы ссылка Log In была доступна тем пользователям, которые еще не вошли в систему (гости, пользователи без роли ROLE_USER). Мы будем рассматривать оба подхода для добавления этой функциональности и начнем с Spring Security JSP Tag Libray (библиотеку тегов).

Использование Spring Security библиотеки тегов для условной визуализации контента

Как мы видели во второй части Spring Security теги могут использоваться для доступа к данным содержащихся в Authentication объекте. Мы рассмотрим другую мощную функциональность библиотеки тегов. Чаще всего в Spring Security библиотеке тегов используется функциональность для условной визуализации частей страницы, основанной на правилах авторизации. Это достигается за счет тега <authorize>, который похож на <if> тег в core JSTL библиотеке, в этом теге мы будем отображать, в зависимости от условий, часть страницы.

Условная визуализация основанная на URL правилах доступа

Spring Security библиотека тегов предоставляет функциональность для визуализации контента основанного на URL правилах авторизации, которые определяются в конфигурационном файле приложения. Это делается с помощью <authorize> тега, с url атрибутом.

Например, мы можем сделать так, чтобы ссылка My Account отображалась только для пользователей, зашедших в систему. Напомним, что предыдущие правила доступа мы определяли следующим образом:

                        
<intercept-url pattern="/account/*.do"
access="hasRole('ROLE_USER') and fullyAuthenticated"/>
		

И так, JSP код для условного отображения ссылки My Account будет выглядеть следующим образом:

                        
<sec:authorize url="/account/home.do">
<c:url value="/account/home.do" var="accountUrl"/>
<li><a href="${accountUrl}">My Account</a></li>
</sec:authorize>
		

Это гарантирует, что если пользователь не имеет доступа к указанному URL, то ссылка не будет отображаться. Это можно изучить немного глубже, используя HTTP метод проверки авторизации:

                        
<sec:authorize url="/account/home.do" method="GET">
<c:url value="/account/home.do" var="accountUrl"/>
<li><a href="${accountUrl}">My Account</a> (with 'url' attr)</li>
</sec:authorize>
		

Для многих целей, использование url атрибута <authorize> тега будет достаточно, чтобы отобразить часть страницы.

Визуализация основанная на Spring EL выражениях

В дополнение, более гибкий метод для контролирования JSP контента доступен тогда, когда <authorize> тег используется вместе с Spring выражениями.

Напомним, из нашего первоначального исследования в первой части, что Spring выражения это мощное средство. Можно изменить предыдущий пример путем использования SpEL выражений в <authorize> теге для блокирования доступа к My Account ссылке. Выглядит это следующим образом:

                        
<sec:authorize access="hasRole('ROLE_USER') and fullyAuthenticated">
    <c:url value="/account/home.do" var="accountUrl"/>
    <li><a href="${accountUrl}">My Account</a> (with 'access' attr)</li>
</sec:authorize>
		

Реализацию этого в Spring 2 мы рассматривать не будем.

Использование логики контроллера для условной визуализации содержания

Давайте сейчас адаптируем примеры, которые мы только что делали с использованием тега <authorize>.

Добавление условного отображения Log In ссылки

Вместо использования Spring Security <authorize> тега, мы сделаем так, что Boolean переменная в модели данных будет доступна view. Она будет говорить, использовать или нет Log In ссылку. Здесь мы будем использовать Java Standart Tag Library (JSTL) тег if, вместе с JSP EL выражением, для условного отображения части страницы.

                        
<c:if test="${showLoginLink}">
<c:url value="/login.do" var="loginUrl"/>
<li><a href="${loginUrl}">Log In</a></li>
</c:if>
		

Заполнение модели данных основанной на полномочиях пользователя

Объектная модель наших контроллеров установлена в объектно-ориентированном шаблоне со всеми Spring MVC контроллерами, расширяя один базовый класс BaseController. Это дает нам место для расположения там часто используемого кода и делает доступным для всех контроллеров в приложении. Первое, мы положим в метод, который будет получать Authentication реализацию из текущего запроса:

                        
protected Authentication getAuthentication() {
  return SecurityContextHolder.getContext().getAuthentication();	
}
		

Далее, мы добавим метод, который заполняет showLoginLink значением модели данных, используя Spring MVC метод с аннотацией:

                        
@ModelAttribute("showLoginLink")
public boolean getShowLoginLink() {
  for (GrantedAuthority authority : getAuthentication().
  getAuthorities()) {
    if(authority.getAuthority().equals("ROLE_USER")) {
      return false;
    }
  }
  return true;
}

		

Так как этот метод аннотирован как @ModelAttribute, то он будет автоматически запущен Spring MVC, когда любой контроллер запустит расширенный BaseController.

Какой лучший путь настройки in-page авторизации?

Вы должны использовать url атрибут тега когда:

К сожалению, в обычном приложении, вы будете использовать url версию тега не так часто. В реальности приложения немного сложнее и требуют активного участия логики, когда решается вопрос об отображении части страницы.

Хотя это заманчиво - использовать Spring Security библиотеку тегов для указания кусочка отображаемой страницы на основе критериев безопасности которые мы видели в этой части, включая if...Granted и access (SpEL) методы, существует ряд причин (во многих случаях) почему это не самая лучшая идея:

Мы видим, что JSP подход удобен для условной визуализации содержания, но имеет и свои слабые стороны.
Все эти вопросы могут быть решены через использование кода в контроллере, который может передавать данные в view модель приложения.

Защита бизнес слоя

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

Spring Security имеет две главных техники для защиты методов:

Основы защиты бизнес методов

Давайте возьмем определенный бизнес метод в нашем приложении и проиллюстрируем, как применить к нему правила.
Как часть реорганизации приложения на три части, мы абстрагировали функциональность смены пароля рассматриваемую в ранних частях в бизнес уровень. Вместо того, чтобы делать вызов напрямую из Web MVC контроллера к JDBC DAO, мы сделали разумный выбор, вставить бизнес сервис который может предоставить дополнительную функциональность, если будет нужно. Это проилюстрированно в следующей диаграмме:

Spring Security

Мы можем видеть в этом примере, что com.packtpub.springsecurity.service.IUserService интерфейс может представлять бизнес уровень архитектуры приложения. Это подходящее место для нас, чтобы добавить безопасность на уровне метода.

Добавление @PreAuthorize аннотации метода

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

                        
public interface IUserService {
  @PreAuthorize("hasRole('ROLE_USER')")
  public void changePassword(String username, String password);
}
		

Этого будет достаточно, чтобы гарантировать, что действительный пользователь имеет доступ к функции смены пароля. Spring Security будет использовать во время запуска aspect oriented programming (AOP) pointcut (pointcut аспектно-ориентированного программирования) для запуска before advice на методах и выбрасывания AccessDeniedException, если огриничения в плане безопасности не будут выполнены.

Инструкции для Spring Security для использования аннотаций методов

Мы также должны сделать изменения в dogstore-security.xml. Просто добавим следующий элемент сразу перед <http> объявлением:

                        
<global-method-security pre-post-annotations="enabled"/>
		

Проверка безопасности метода

Неужели все так просто? Попробуйте изменить ROLE_USER объявление на ROLE_ADMIN. Зайдите в систему под guest и попробуйте поменять пароль. В итоге вы получите ошибку HTTP Status 403.

Если вы посмотрите в консоль Tomcat, вы увидите длинный стек:

                        
DEBUG - Could not complete request
o.s.s.access.AccessDeniedException: Access is denied
at o.s.s.access.vote.AffirmativeBased.decide
at o.s.s.access.intercept.AbstractSecurityInterceptor.beforeInvocation
...
at $Proxy12.changePassword(Unknown Source)
at com.packtpub.springsecurity.web.controller.AccountController.
submitChangePasswordPage
		

Еще несколько защит для методов

В дополнение к @PreAuthorize аннотации существует несколько других путей для объявления предварительной авторизации для методов. Мы будем рассматривать эти пути защиты методов и затем рассмотрим их плюсы и минусы при различных обстоятельствах.

JSR-250 совместимые стандартные правила

JSR-250, Общие Аннотации для Java платформы, определяют серию аннотаций, которые предназначены для переноса через JSR-250 совместимых сред выполнения. Spring Framework является совместимым с JSR-250 как часть Spring 2.x, включая Spring Security Framework.

Пока JSR-250 аннотации не так выразительны как родные Spring аннотации, но они имеют преимущество в том, что они обеспечивают совместимость между реализациями Java EE application серверов таких как Glassfish, или Apache Tuscany.

Чтобы реализовать правило мы должны сделать два изменения, первое в dogstore-security.xml файле:

                        
<global-method-security jsr250-annotations="enabled"/>
		

Второе, изменить аннотацию @PreAuthorize на @RolesAllowed. Аннотация @RolesAllowed не поддерживает SpEL выражения, поэтому это выглядит как URL правило авторизации, которые мы рассматривали в первой части. Мы редактируем IUserService интерфейс:

                        
@RolesAllowed("ROLE_USER")
public void changePassword(String username, String password);
		

Можете протестировать ваше приложение изменив пользователя на администратора как и в предыдущем упражнении.

Заметьте, что также есть возможность предоставлять список разрешенных GrantedAuthority имен, используя стандартный Java 5 String массив:

                        
@RolesAllowed({"ROLE_USER","ROLE_ADMIN"})
public void changePassword(String username, String password);
		

Также существуют еще две дополнительные аннотации определенные JSR-250, @PermitAll и @DenyAll, разрешает и запрещает все запросы к методам, соответственно.

Мы, чуть позже, рассмотрим, как JSR-250 стиль аннотаций может существовать вместе с Spring Security аннотациями.

Безопасность методов через использование Spring @Secured аннотации

Spring сам обеспечивает простой стиль аннотаций, который похож на JSR-250 @RolesAllowed аннотацию. @Secured аннотация функционально и синтаксически такая же как и @RolesAllowed. Только есть одно различие - для обработки этих аннотаций нужно явно включить еще один атрибут в элементе <global-method-security>:

                        
<global-method-security secured-annotations="enabled"/>
		

Эту аннотацию не стоит использовать в новом коде, который вы пишете, используйте ее со старой версией Spring.

Правила защиты методов, используя аспектно-ориентированное программирование

Последняя техника для защиты методов и, вполне возможно, самая мощная, с дополнительными преимуществами, не требующими изменения всего кода. Вместо этого, вы используете аспектно-ориентированное программирование для объявления pointcut у метода или набора методов, с advice, который выполняет проверку для ролей, когда pointcut совпадают. AOP объявления представлены в Spring Security XML конфигурационном файле, и не связан с аннотациями.

Следующий пример защищает все методы интерфейса сервиса с административными правами:

                        
<global-method-security>
<protect-pointcut access="ROLE_ADMIN" expression="execution(* com.
packtpub.springsecurity.service.I*Service.*(..))"/>
</global-method-security>
		

Как работает защита методов?

Механизм предоставления доступа для защиты методов - разрешен или нет данный запрос - концептуально такой же как и логика предоставления доступа для web запроса на доступ.
AccessDecisionManager опрашивает набор AccessDecisionVoters, каждый из которых может предоставлять решение о разрешении или запрете доступа или воздерживаться от голосования.
Конкретная реализация AccessDecisionManager агрегирует решения голосующих и формирует общее решение о вызове метода.

Следующая диаграмма показывает основных игроков вовлеченных в решение об авторизации для выполнения метода:

Spring Security

Мы видим, что Spring Security o.s.s.access.intercept.aopalliance.MethodSecurityInterceptor вызывается стандартным Spring AOP, перехватывая вызов метода.

Как это работает? Очевидно, что MethodSecurityInterceptor не может вызываться для каждого вызова метода в приложении - так как же аннотации на методах или классах приводят к перехвату AOP?

Прежде всего, AOP не вызывается для всех Spring-управляемых бинов по умолчанию. Вместо этого, если <global-method-security> определен в Spring Security конфигурации, то будет зарегистрирован стандартный Spring AOP o.s.beans.factory.config.BeanPostProcessor, который будет анализировать AOP конфигурацию чтобы увидеть какие-либо AOP advisors, показывающие, что требуется proxying (и перехват). Этот процесс является стандартом Spring AOP обработки (известный как AOP auto-proxying) и по своей функциональности не является спецификой Spring Security. Все зарегистрированные BeanPostProcessor запускаются при иницилизации spring ApplicationContext, после всех Spring Bean конфигураций.

AOP auto-proxy опрашивает все зарегистрированные PointcutAdvisors, для того, чтобы наблюдать за AOP pointcuts, в случае если они решат выполнить метод, который имеет AOP advice. Spring Security реализует o.s.s.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor класс, который анализирует любые настроенные безопасности методов и устанавливает соответствующий AOP перехват. Запомните, что только интерфейсы или классы с объявленными правилами защиты методов, будут proxied для AOP!

Заметка: Настоятельно рекомендуется объявлять правила AOP ( и другие аннотации безопасности) на интерфейсах, но не на реализациях.

MethodSecurityMetadataSourceAdvisor делегирует решение влиятьна методы с AOP advice экземпляру o.s.s.access.method.MethodSecurityMetadataSource. Различные формы аннотаций безопасности методов имеют свою реализацию MethodSecurityMetadataSource, которая используется для анализа каждого метода и класса по очереди и добавляет AOP advice для выполнения во время запуска.

Следующая диаграмма показывает как это происходит:

Spring Security

Расширенная безопасность методов

Безопасность методов не заканчивается на простой проверке ролей. В действительности, некоторые аннотации безопасности методов могут использовать полную мощь Spring Expression Language (SpEL), который мы рассматривали в первой части, когда говорили об правилах авторизации в перехвате URL. Это означает, что можно составлять любые выражения с расчетом, с булевой логикой итп.

Правила защиты методов использующие bean декораторы

Альтернативная форма для объявления правил безопасности методов включает использование декларативного XML, который может включать определение Spring Bean. Эта форма безопасности методов более выразительна чем pointcuts и не на столько обширная как подход основанный на аннотациях, который мы рассматривали. Тем не менее, для некоторых типов проектов, будет достаточным использовать XML декларативный подход.

Мы заменим правила, объявленные в предыдущем примере, на XML объявления для защиты changePassword метода. Мы использовали bean auto-wiring (автоматическое связывание бинов), который к сожалению не поддерживается XML методом, поэтому мы должны явно объявить сервисный слой бинов для демонстрации этой техники.

Декораторы безопасности - часть пространства имен безопасности XML. Первым делом мы должны включить schema безопасности в dogstore-base.xml файл, который, на данный момент, используется определения Spring бинов.

                        
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:security="http://www.springframework.org/schema/security"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/aop http://www.
  springframework.org/schema/aop/spring-aop-3.0.xsd
  http://www.springframework.org/schema/jdbc http://www.
  springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
  http://www.springframework.org/schema/context http://www.
  springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/security http://www.
  springframework.org/schema/security/spring-security-3.0.xsd
">
		

Далее, нужно удалить все аннотации безопасности, которые могут быть в IUserService.changePassword методе.

В конце, объявим бин с синтаксисом Spring XML, с дополнительным декоратором, который будет указывать, что любой, кто хочет вызвать метод changePassword должен иметь роль ROLE_USER:

                        
<bean id="userService" class="com.packtpub.springsecurity.service.
UserServiceImpl">
  <security:intercept-methods>
    <security:protect access="ROLE_USER" method="changePassword"/>
  </security:intercept-methods>
</bean>
		

Если рассматривать работу, то данный тип функциональности защиты методов использует MethodSecurityInterceptor связанный с MapBasedMethodSecurityMetadataSource, который использует перехватчик для определения соответствующего ConfigAttributes доступа.

Правила защиты метода включая параметры метода

Логично, что написание правил, которые относятся к параметрам метода, относящиеся к ограничениям, является разумным для некоторых типов операций. Например, возможно, имеет смысл ограничить доступ к методу changePassword, так, что пользователь пытающийся выполнить метод должен удовлетворять двум ограничениям:

Не так сложно сделать, чтобы ограничить выполнение метода только для администратора, и не так ясно как мы должны определить, если пользователь пожелает изменить свой пароль.

К счастью, SpEL связывание, используемое аннотациями методов Spring Security поддерживает более сложные выражения, включая выражения которые включают в себя параметры метода.

                        
@PreAuthorize("#username == principal.username and hasRole('ROLE_USER')")
public void changePassword(String username, String password);
		

Вы можете видеть здесть дополнительное объявление SpEL, которое мы использовали в первом уроке для сверки имя пользователя принципала с именем пользователя в параметре метода (#username - имя параметра метода с префиксом #). В действительности это мощное средство связывания параметров метода.

Как работает связывание параметров метода

Использование очень похоже на то, что мы видели в первой части с выражениями авторизации <intercept-url>, обработчик выражений - реализация интерфейса o.s.s.access.expression.method.MethodSecurityExpressionHandler - ответственного за создание SpEL контекста в котором определяются выражения. MethodSecurityExpressionHandler использует o.s.s.access.expression.method.MethodSecurityExpressionRoot как корень выражения (root), который (как и WebSecurityExpressionRoot, используемый для выражений URL авторизации) предоставляет набор методов и псевдо-свойства для определения SpEL выражений.

Почти все кусочки выражений “из коробки” (например, hasRole) доступны в контексте защиты метода, плюс еще один метод был связан со списком проверки контроля доступа (который мы будем обсуждать позже в шестой части) и другие псевдо-свойства которые включают мощную фильтрацию данных основанной на ролях.

Вы можете заметить, что псевдо-свойство principal, используемой в предыдущем примере, один из доступных операндов, которые являются полезными выражениями при защите методов чем SpEL выражения на web уровне. Псевдо-свойство principal будет возвращать принципала расположенного в текущем Authentication объекте, обычно либо это String(username) или UserDetails реализация - это означает, что любые UserDetails свойства или методы могут быть использованы для совершенствования метода ограничения доступа.

Следующая диаграмма показывает функциональность этой части:

Spring Security

Переменные SpEL указываются с хеш префиксом #. Одно вожное обстоятельство заключается в том, что для метода имена аргументов будут доступны во время выполнения, отладочную информацию таблицы символов нужно сохранить после компиляции. Общие методы для включения этого перечислены ниже:

Защита данных метода через фильтрацию основанную на ролях

Две последние Spring Security-зависимые аннотации - @PreFilter и @PostFilter, которые используются для применения фильтрации защиты к Collections и Arrays ( только с @PostFilter ). Этот тип функциональности называется security trimming ( обрезка ) или security prining. Они связаны с использованием полномочий безопасности принципала во время выполнения для избирательного удаления членов из набора объектов. Как вы могли предположить, эта фильтрация выполняется при использовании SpEL выражений с аннотациями.

Мы рассмотрим это на примере. Например, мы хотим отображать специальную категорию для пользователей, кто имеет учетную запись в системе, называемую Customer Appreciation Specials. В дополнение, мы хотим использовать свойство customerOnly хранилища объекта Category, чтобы гарантировать, что некоторые категории продуктов отображаются только для клиентов магазина.

com.packtpub.springsecurity.web.controller.HomeController, который используется для отображения домашней страницы, имеет код для показа категорий - Collection объектов Category:

                        
@Controller
public class HomeController extends BaseController {
  @Autowired
  private IProductService productService;
  @ModelAttribute("categories")
  public Collection<Category> getCategories() {
    return productService.getCategories();
  }
  @RequestMapping(method=RequestMethod.GET,value="/home.do")
  public void home() {
  }
}
		

Бизнес слой IProductService реализация, делегирует слою данных IProductDao провайдеру. Для целей упрощения, IProductDao реализация жестко закодирована в списке объектов Category.

Добавление фильтрации данных на основе ролей с @PostFilter

Давайте добавим @PostFilter в бизнес слой. Синтаксис будет выглядеть следующим образом:

                        
@PostFilter("(!filterObject.customersOnly) or (filterObject.
customersOnly and hasRole('ROLE_USER'))")
Collection<Category> getCategories();
		

Давайте рассмотрим поток для аннотации @PostFilter:

Мы видим, что снова используются стандартные конструкции Spring AOP, o.s.s.access.expression.method.ExpressionBasedPostInvocationAdvice запускается в after AOP обработчике и этот advice используется для фильтрования Collection или Array возвращенного из целевого метода. Как и в аннотации обработки @PreAuthorize, DefaultMethodSecurityExpressionHandler снова ответственен за построение SpEL определение контекста и определение выражений.

Все, можно смотреть результат наших изменений.