【问题标题】:Can anyone give an insight to how the @PreAuthorize annotation works?任何人都可以深入了解@PreAuthorize 注释的工作原理吗?
【发布时间】:2021-10-14 14:05:59
【问题描述】:

我知道 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触发特定方法的。

【问题讨论】:

    标签: java spring spring-boot spring-security


    【解决方案1】:

    @PreAuthorize 等于@Secured 和@RolesAllowed。

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

    但是 @PreAuthorize 的 String 参数是一个 SpEL 表达式 使用 SpEL 表达式 指导访问决策,可以编写更高级的安全约束

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

    【讨论】:

      【解决方案2】:

      Spring security 使用面向方面的编程 (AOP) 将安全代码编织/交织到您自己的代码库中。这在 Spring 中的工作方式是使用定义注入点的注释 (cfr.pointcuts) 以允许在您自己的代码之前/之后/内部执行额外的逻辑 (cfr.advice)。

      Interceptors 扫描您的代码库以查找join points(即,对于 Spring,当使用特定注释标记时,这始终是方法执行),并将根据您使用的拦截点(即接口)执行额外的特定逻辑。

      要启用此行为,可以添加配置:

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

      特别是对于PreAuthorizePrePostAdviceReactiveMethodInterceptor 负责查找带有PreAutorize 注释的方法。反过来,这将委托给PreInvocationAuthorizationAdvice,在此处将ReactiveMethodSecurityConfiguration 配置为ExpressionBasedPreInvocationAdvice

      这在内部使用默认表达式处理程序DefaultMethodSecurityExpressionHandler 创建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);
        }
      }
      

      【讨论】:

      • 非常感谢队友。
      【解决方案3】:

      注解什么都不做,只不过是元数据。向方法添加注释并且没有作用于该注释的一段代码,它什么也不做。所以你需要某种注释处理器。

      对于 Spring 中使用的大多数注解,这意味着它们是由 AOP(面向方面​​编程)应用的。在 Spring 中,这意味着默认情况下,这是基于代理完成的。这个代理拦截实际的方法调用,在调用实际方法之前/之后应用一些额外的逻辑。

      当使用基于 AspectJ 的方面时,附加逻辑由 MethodInterceptor@Aspect 提供。对于@PreAuthorize 注释,这是由MethodSecurityInterceptor 完成的。

      MethodSecurityInterceptor 所做的是,根据@PreAuthorize 注释和其中写入的表达式(表达式是 SpEL 表达式)确定当前用户是否有权访问该方法。

      MethodSescurityInterceptor 可以手动启用(自己进行整个配置,非常繁琐且容易出错)或添加@EnableGlobalMethodSecurifty 注释。后者将添加一个@Configuration 类和一些处理器来自动配置所需的基础设施类。

      【讨论】:

        【解决方案4】:

        在评估@PreAuthorize 注解中给出的字符串之后的方面调用。

        有关详细信息,我认为您需要深入研究 Spring 源代码。也许从这里开始:https://github.com/spring-projects/spring-security/blob/main/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java#nn

        【讨论】:

          猜你喜欢
          • 2011-03-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-28
          • 2021-04-11
          • 2016-09-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多