Главная » Spring » Защита с помощью аннотаций, выполняемых до и после вызова Spring

0

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

В Spring Security 3.0 появилось несколько новых аннотаций, по- зволяющих использовать выражения на языке SpEL для реализации более сложных ограничений на доступ к методам. Эти новые анно- тации перечислены в табл. 10.6.

Таблица 10.6. Spring Security 3.0 предлагает четыре новые аннотации для защиты методов с применением выражений на языке SpEL

Аннотация

Описание

@PreAuthorize

Ограничивает доступ к методам перед их вызовом, опираясь на результат вычисления выражения

@PostAuthorize

Позволяет вызывать методы, но возбуждает исключение, если выражение возвращает значение false

@PostFilter

Позволяет вызывать методы, но фильтрует его результаты в соответствии со значением выражения

@PreFilter

Позволяет вызывать методы, но фильтрует входные данные перед фактическим вызовом метода

Примеры использования каждой из них будут представлены чуть ниже. Но прежде чем использовать, их поддержку необходимо включить в элементе <global-method-security>, указав значение enabled в  атрибуте  pre-post-annotations:

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

После включения поддержки описываемых аннотаций можно приступать к их использованию для защиты методов. Начнем с ан- нотации @PreAuthorize.

Проверка условия перед вызовом метода

На первый взгляд может показаться, что аннотация @PreAuthorize является всего лишь эквивалентом аннотаций @Secured и @RolesAllowed, обладающим поддержкой выражений на языке SpEL. Фактически аннотацию @PreAuthorize можно использовать для ограничения до- ступа на основе привилегий, которыми обладает авторизованный пользователь:

@PreAuthorize("hasRole(‘ROLE_SPITTER’)") public   void   addSpittle(Spittle   spittle)   {

// …

}

Аннотация @PreAuthorize принимает строковый аргумент с вы- ражением на языке SpEL. В примере выше используется функция hasRole(), предоставляемая фреймворком Spring Security, разрешаю- щая доступ к методу, только если авторизованный пользователь об- ладает привилегией ROLE_SPITTER.

Однако на языке SpEL можно выразить гораздо более сложные требования. Например, представьте, что необходимо ограничить длину сообщений, посылаемых обычным пользователем, 140 сим- волами и не применять это ограничение к привилегированному пользователю. Аннотации @Secured и @RolesAllowed оказались бы бес- полезны в этом случае, а аннотация @PreAuthorize позволяет легко справиться с данной задачей:

@PreAuthorize("(hasRole(‘ROLE_SPITTER’)  and  #spittle.text.length()  <=  140) or hasRole(‘ROLE_PREMIUM’)")

public  void  addSpittle(Spittle  spittle)  {

// …

}

Фрагмент #spittle выражения является непосредственной ссыл- кой на параметр метода с тем же именем. Эта возможность позво- ляет фреймворку Spring Security исследовать параметры методов и использовать их в выражениях при принятии решения. Здесь про- веряется длина свойства text объекта Spitter, чтобы убедиться, что

она не превышает допустимого значения, если метод вызывается обычным пользователем. А если пользователь является привилеги- рованным пользователем, длина сообщения не имеет значения.

Проверка условия после вызова метода

Менее очевидный способ защиты методов заключается в проверке условия после вызова метода. Обычно в этом случае решение при- нимается на основе объекта, возвращаемого защищенным методом. Разумеется, это означает, что метод должен быть вызван и вернуть некоторое значение.

Помимо момента принятия решения, аннотация @PostAuthorize действует практически так же, как и аннотация @PreAuthorize. Напри- мер, предположим, что необходимо защитить метод getSpittleById(), позволив возвращать объект Spittle, только если он принадлежит авторизованному пользователю. Для этого можно было бы снабдить метод getSpittleById() аннотацией @PostAuthorize, как показано ниже:

@PostAuthorize("returnObject.spitter.username  ==  principal.username") public  Spittle  getSpittleById(long  id)  {

// …

}

Чтобы упростить доступ к объекту, возвращаемому защищенным методом, фреймворк Spring Security предоставляет имя returnObject в языке SpEL. В данном случае известно, что метод возвращает объект типа Spittle, поэтому выражение обращается к его свойству spitter и извлекает из него значение его свойства username.

С другой стороны оператора сравнения (==) используется встро- енный объект principal, из которого извлекается значение свойства username. Объект principal – это еще одно специальное встроенное имя, предоставляемое фреймворком Spring Security, которое ссыла- ется на главный объект, представляющий текущего авторизованного пользователя.

Если объект Spittle ссылается на объект Spitter, значение свой- ства username которого совпадает со значением свойства username объ- екта principal, то объект Spittle будет возвращен вызывающей про- грамме. Иначе будет возбуждено исключение AccessDeniedException и вызывающая программа не получит объекта Spittle.

Имейте в виду, что, в отличие от методов, снабженных аннотацией

@PreAuthorize, методы с аннотацией @PostAuthorize будут сначала вы- полнены, и только потом будет произведена проверка. Это означает,

что перед применением аннотации @PostAuthorize необходимо убе- диться в отсутствии побочных эффектов в методе, которые могут привести к нарушению системы безопасности.

Фильтрация после вызова метода

Иногда бывает необходимо защитить не сам метод, а данные, воз- вращаемые этим методом. Например, представьте, что необходимо вернуть список сообщений пользователю, но при этом список должен содержать только сообщения, которые пользователь может удалить. В этом случае можно было бы аннотировать метод, как показано ниже:

@PreAuthorize("hasRole(‘ROLE_SPITTER)")

@PostFilter("filterObject.spitter.username    ==    principal.name") public  List<Spittle>  getABunchOfSpittles()  {

}

Здесь аннотация @PreAuthorize позволяет вызывать метод лишь пользователям с привилегией ROLE_SPITTER. Если пользователь прой- дет эту проверку, метод будет вызван и вернет список сообщений. Но аннотация @PostFilter отфильтрует этот список, оставив в нем только сообщения (объекты Spittle), принадлежащие пользователю.

Имя filterObject в выражении ссылается на отдельные элемен- ты в списке (которые, как мы знаем, являются объектами Spittle), возвращаемом методом. Если свойство username свойства spitter в этом объекте совпадает с именем авторизованного пользователя (principal.name в выражении), тогда элемент остается в отфильтро- ванном списке. Иначе он удаляется.

Я знаю, что вы сейчас подумали. Можно было бы написать запрос, возвращающий только сообщения текущего пользователя. Такое ре- шение можно было бы использовать, если бы правила позволяли пользователям удалять лишь свои сообщения.

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

@PreAuthorize("hasRole(‘ROLE_SPITTER)")

@PostFilter("hasPermission(filterObject,    ‘delete’)") public  List<Spittle>  getSpittlesToDelete()  {

}

В данном случае функция hasPermission() должна вернуть true, ес- ли пользователь обладает разрешением delete на удаление сообще- ния, на которое ссылается имя filterObject. Я сказал, что она должна вернуть true, но в действительности hasPermission() по умолчанию всегда возвращает false.

Если hasPermission() всегда по умолчанию возвращает false, тогда какой смысл использовать эту функцию? Вся прелесть поведения по умолчанию – в том, что его всегда можно переопределить. Чтобы пе- реопределить поведение функции hasPermission(), необходимо создать и зарегистрировать обработчик разрешений. Эту функцию берет на себя класс SpittlePermissionEvaluator, представленный в листинге 10.7.

Листинг 10.7. Обработчик разрешений, стоящий позади функции hasPermission()

package  com.habuma.spitter.security; import   java.io.Serializable;

import   org.springframework.security.access.PermissionEvaluator; import    org.springframework.security.core.Authentication;   import   com.habuma.spitter.domain.Spittle;

public  class  SpittlePermissionEvaluator  implements  PermissionEvaluator  { public  boolean  hasPermission(Authentication  authentication,

Object target, Object permission) { if  (target  instanceof  Spittle)  {

Spittle   spittle   =   (Spittle)   target; if  ("delete".equals(permission))  {

return spittle.getSpitter().getUsername().equals( authentication.getName()) || hasProfanity(spittle);

}

}

throw   new   UnsupportedOperationException(

"hasPermission  not  supported  for  object  <"  +  target

+  ">  and  permission  <"  +  permission  +  ">");

}

public  boolean  hasPermission(Authentication  authentication,

Serializable  targetId,  String  targetType,  Object  permission)  { throw new  UnsupportedOperationException();

}

private  boolean  hasProfanity(Spittle  spittle)  {

return false;

}

}

Класс SpittlePermissionEvaluator реализует интерфейс Permission- Evaluator, требующий реализации двух различных методов hasPer- mission(). Один метод hasPermission() принимает два объекта Object. Первый – который требуется оценить, и второй – который опре- деляет искомое разрешение. Второй метод hasPermission() может пригодиться, только когда доступен идентификатор оцениваемого объекта, который передается во втором параметре в виде объекта Serializable.

В данной ситуации предположим, что в оценке разрешений всег- да будут участвовать только объекты Spittle, поэтому второй метод просто возбуждает исключение UnsupportedOperationException.

Что касается первого метода hasPermission(), он проверяет, явля- ется ли оцениваемый объект экземпляром класса Spittle, а объект искомого разрешения является разрешением на удаление. Если эти условия соблюдаются, метод сравнивает имя пользователя, создав- шего сообщение, с именем текущего авторизованного пользователя, и проверяет наличие ненормативной лексики в тексте сообщения, передавая его методу hasProfanity()1.

После реализации обработчика разрешений необходимо зарегист- рировать его в Spring Security, чтобы обеспечить поддержку функ- ции hasPermission() в выражении, указанном в аннотации @PostFilter. Поэтому необходимо создать компонент обработчика выражений и зарегистрировать его с помощью элемента <global-method-security>.

Для этого создадим компонент типа DefaultMethodSecurityExpressio nHandler и внедрим в его свойство permissionEvaluator экземпляр на- шего класса SpittlePermissionEvaluator:

<beans:bean id="expressionHandler" class= "org.springframework.security.access.expression.method.

➥DefaultMethodSecurityExpressionHandler">

<beans:property  name="permissionEvaluator">

<beans:bean class= "com.habuma.spitter.security.SpittlePermissionEvaluator"     />

</beans:property>

</beans:bean>

Затем можно приступать к настройке компонента expressionHandler

в элементе <global-method-security>, как показано ниже:

1    Оставляю реализацию метода hasProfanity() на усмотрение читателей.

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

<expression-handler ref="expressionHandler"/>

</global-method-security>

Прежде, при настройке элемента <global-method-security>, мы не указывали обработчика выражений. Но теперь мы заменили обра- ботчик по умолчанию собственной реализацией, которая способна оценивать наличие разрешения.

Источник:   Уоллс К., Spring в действии. – М.: ДМК Пресс, 2013. – 752 с.: ил.

По теме:

  • Комментарии