Может ли кто-нибудь дать представление о том, как работает аннотация @PreAuthorize?

#java #spring #spring-boot #spring-security

Вопрос:

Я знаю, что у Spring Security есть абстрактный класс SecurityExpressionRoot . В этом у нас реализованы такие методы , как hasAuthority(String var1) и hasRole(String var1) т. Д. Spring также предоставляет @PreAuthorize аннотацию, которая будет использоваться на уровне метода, мы передаем одно значение в этой аннотации, например

 @PreAuthorize("hasRole('ROLE_ABC')")
 

Аннотация @interface выглядит так

 package org.springframework.security.access.prepost;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
    String value();
}
 

Я хочу знать, как эта аннотация запускает конкретный метод SecurityExpressionRoot .

Ответ №1:

Spring security использует аспектно-ориентированное программирование (AOP) для вплетения/переплетения кода безопасности в вашу собственную базу кода. Весной это работает с помощью аннотаций, определяющих точки ввода (cfr. pointcuts ), чтобы разрешить выполнение дополнительной логики до/после/в вашем собственном коде (cfr. advice ).

Interceptors отсканируйте свою кодовую базу на join points предмет (т. Е. для Spring это всегда выполнение метода, отмеченное конкретными аннотациями) и выполнит дополнительную конкретную логику в зависимости от того, какую точку перехвата (т. е. интерфейс) вы использовали.

Чтобы включить такое поведение, можно добавить конфигурацию:

 @Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class ConfigGlobalMethodSecurity extends GlobalMethodSecurityConfiguration {
  ...
}
 

В частности, для предварительной авторизации PrePostAdviceReactiveMethodInterceptor отвечает за поиск ваших методов с аннотациями PreAutorize . В свою очередь, это приведет к делегированию, для PreInvocationAuthorizationAdvice которого настроена здесь конфигурация ReactiveMethodSecurityConfiguration как ExpressionBasedPreInvocationAdvice.

Внутренне при этом используется обработчик выражений по умолчанию Defaultmethod Securityexpressionhandler, который создает SecurityExpressionRoot . Фактическая реализация этого SecurityExpressionRoot определит, как будет обрабатываться ваше выражение в вашей PreAuthorize воле и какая логика должна быть выполнена.

Корень SecurityExpressionRoot определяет , какие выражения разрешены в вашем PreAuthorize , например hasRole .

Чтобы добавить дополнительные выражения или расширить логику разрешений по умолчанию, вам необходимо предоставить пользовательскую реализацию SecurityExpressionRoot с дополнительным пользовательским PermissionEvaluator . Например, если вы хотите написать @PreAuthorize("hasKnowledgeOf('AOP')") .

 public class CustomMethodSecurityExpressionRoot
    extends SecurityExpressionRoot
    implements MethodSecurityExpressionOperations {

  private final PermissionEvaluator permissionEvaluator;
  private final Authentication authentication;

  private Object filterObject;
  private Object returnObject;
  private Object target;

  public CustomMethodSecurityExpressionRoot(
      Authentication authentication,
      PermissionEvaluator permissionEvaluator) {
    super(authentication);
    this.authentication = authentication;
    this.permissionEvaluator = permissionEvaluator;
    super.setPermissionEvaluator(permissionEvaluator);
  }

  // new expression to check if the requested knowledge is present
  public boolean hasKnowledgeOf(String context) {
    // provide logic that performs the check
  }

  @Override
  public void setFilterObject(Object filterObject) {
    this.filterObject = filterObject;
  }

  @Override
  public Object getFilterObject() {
    return filterObject;
  }

  @Override
  public void setReturnObject(Object returnObject) {
    this.returnObject = returnObject;
  }

  @Override
  public Object getReturnObject() {
    return returnObject;
  }

  @Override
  public Object getThis() {
    return target;
  }
}
 

и

 @Configuration
public class CustomPermissionEvaluator
    implements PermissionEvaluator {

  @Override
  public boolean hasPermission(
      Authentication authentication,
      Object targetDomainObject,
      Object permission) {
    // define your custom permission logic here
  }

  @Override
  public boolean hasPermission(
      Authentication authentication,
      Serializable targetId,
      String targetType,
      Object permission) {
    // define your custom permission logic here
  }
}
 

Чтобы завершить настройку и передать средство оценки в корень выражения.

 public class CustomMethodSecurityExpressionHandler
    extends DefaultMethodSecurityExpressionHandler {

  PermissionEvaluator permissionEvaluator;

  public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
    this.permissionEvaluator = permissionEvaluator;
    super.setPermissionEvaluator(permissionEvaluator);
  }

  @Override
  protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
      Authentication authentication,
      MethodInvocation invocation) {
    CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(
        authentication,
        permissionEvaluator);
    root.setTrustResolver(new AuthenticationTrustResolverImpl());
    root.setRoleHierarchy(getRoleHierarchy());
    return root;
  }
}
 

и

 @Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class ConfigGlobalMethodSecurity extends GlobalMethodSecurityConfiguration {

  @Autowired CustomPermissionEvaluator permissionEvaluator;

  @Override
  protected MethodSecurityExpressionHandler createExpressionHandler() {
    return new CustomMethodSecurityExpressionHandler(permissionEvaluator);
  }
}
 

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

1. Очень признателен, приятель.

Ответ №2:

Аннотация ничего не делает и является не чем иным, как метаданными. Добавление аннотации к методу и без фрагмента кода, который действует на эту аннотацию, ничего не делает. Итак, вам нужен какой-то обработчик аннотаций.

Для большинства аннотаций, используемых в Spring, это означает, что они применяются AOP (Аспектно-ориентированное программирование). Весной это означает, что по умолчанию это делается на основе прокси-сервера. Этот прокси перехватывает фактический вызов метода, применяет некоторую дополнительную логику до/после вызова фактического метода.

Дополнительная логика обеспечивается a MethodInterceptor или @Aspect при использовании аспектов, основанных на AspectJ. В случае @PreAuthorize аннотации это делается с помощью MethodSecurityInterceptor .

Что MethodSecurityInterceptor делает, на основе @PreAuthorize аннотации и выражения, написанного там (выражение является выражением SpEL), определяет, имеет ли текущий пользователь право доступа к методу.

Это MethodSescurityInterceptor может быть включено либо вручную (выполнение всей конфигурации самостоятельно, довольно утомительно и подвержено ошибкам), либо путем добавления @EnableGlobalMethodSecurifty аннотации. Последний добавит @Configuration класс и некоторый процессор для автоматической настройки необходимых классов инфраструктуры.

Ответ №3:

Вызов аспекта после оценки строки, указанной в аннотации @PreAuthorize.

Для получения подробной информации, я думаю, вам нужно покопаться в исходном коде Spring. Может быть, начнем отсюда: https://github.com/spring-projects/spring-security/blob/main/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java#nn

Ответ №4:

@Предварительная авторизация равна @Защищенным и @разрешенным ролям.

 @PreAuthorize("hasRole('ROLE_SPITTER')")
public void addSpittle(Spittle spittle) {
// ...
}
 

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

 @PreAuthorize(
"(hasRole('ROLE_SPITTER') and #spittle.text.length() <= 140)"
 "or hasRole('ROLE_PREMIUM')")
public void addSpittle(Spittle spittle) {
// ...
}