【问题标题】:Creating custom PostAuthorize method in Spring Security在 Spring Security 中创建自定义 PostAuthorize 方法
【发布时间】:2014-04-23 07:01:03
【问题描述】:

我正在尝试创建一个自定义方法,用于像这样的 Pre/Post Authorize 调用:

public class CustomLSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler{

    public CustomSecurityExpressionHandler(){
        super();
    }

    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation){
        CustomSecurityExpressionRoot root = new CustomSecurityExpressionRoot(authentication);
        root.setThis(invocation.getThis());
        root.setPermissionEvaluator(getPermissionEvaluator());
        return root;
    }
}

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

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

    public CustomSecurityExpressionRoot(Authentication a) {
        super(a);
    }

    public boolean testDecision(String test){
        System.out.println("Printing:"+test+"\n");
    return true;
    }

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

    public Object getFilterObject() {
        return filterObject;
    }

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

    public Object getReturnObject() {
        return returnObject;
    }

    void setThis(Object target) {
        this.target = target;
    }

    public Object getThis() {
        return target;
    }
    public boolean hasPermission(Object permission) {
        try {
                return super.hasPermission(null, null, permission);
        } catch (AccessDeniedException e) {
                return false;
        }
    }

    public boolean checkPermission(Object permission) {
        return super.hasPermission(null, null, permission);
    }

    @Override
    public boolean hasPermission(Object targetId, String targetType, Object permission) {
        try {
                return super.hasPermission(targetId, targetType, permission);
        } catch (AccessDeniedException e) {
                return false;
        }
    }

    public boolean checkPermission(Object targetId, String targetType, Object permission) {
        return super.hasPermission(targetId, targetType, permission);
    }

    @Override
    public boolean hasPermission(Object target, Object permission) {
       try {
                return super.hasPermission(target, permission);
       } catch (AccessDeniedException e) {
            return false;
       }
    }

    public boolean checkPermission(Object target, Object permission) {
        return super.hasPermission(target, permission);
    }
}

如上所示,我添加了新方法 testDecision(String),我可以在 preAuthorize 调用中成功使用它,如下所示:

@PreAuthorize("testDecision('TestString')")
Event getEvent(int eventId);

但是当我在 PostAuthorize 的上下文中调用它时:

@PostAuthorize("testDecision('TestString')")
Event getEvent(int eventId);

我得到一个 ClassCastException:

SEVERE: Servlet.service() for servlet [Spring MVC Dispatcher Servlet] in context with path [/myapp] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: com.example.CustomSecurityExpressionRoot cannot be cast to org.springframework.security.access.expression.method.MethodSecurityExpressionRoot] with root cause
java.lang.ClassCastException: com.example.CustomSecurityExpressionRoot cannot be cast to org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
    at org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler.setReturnObject(DefaultMethodSecurityExpressionHandler.java:156)
    at org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice.after(ExpressionBasedPostInvocationAdvice.java:49)
    at org.springframework.security.access.prepost.PostInvocationAdviceProvider.decide(PostInvocationAdviceProvider.java:38)
    at org.springframework.security.access.intercept.AfterInvocationProviderManager.decide(AfterInvocationProviderManager.java:73)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.afterInvocation(AbstractSecurityInterceptor.java:282)
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:68)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at com.sun.proxy.$Proxy15.getEvent(Unknown Source)
(..truncated..)

谁能帮我弄清楚我做错了什么?

【问题讨论】:

    标签: java spring spring-security authorization


    【解决方案1】:

    看来您使用的是旧版本的 Spring Security。从 Spring Security 3.1.5+ 开始,SEC-2245 已修复,您可以创建自己的表达式根并实现 MethodSecurityExpressionOperations。

    【讨论】:

      【解决方案2】:

      CustomSecurityExpressionRoot 类必须扩展 MethodSecurityExpressionRoot! (实现MethodSecurityExpressionOperations)是不够的。

      不幸的是MethodSecurityExpressionRoot 是一个包保护类。

      • 因此,您需要将CustomSecurityExpressionRoot 放在同一个包中(org.springframework.security.access.expression.method
      • 或者您将以下类用作CustomSecurityExpressionRoot 的超类(这就是我在项目中所做的)

      ExtensibleMethodSecurityExpressionRoot:

      package org.springframework.security.access.expression.method;
      import org.springframework.security.core.Authentication;
      
      /** Makes the class {@link MethodSecurityExpressionRoot} public to other packages. */
      public class ExtensibleMethodSecurityExpressionRoot extends MethodSecurityExpressionRoot {
      
          /**
           * Instantiates a new extensible method security expression root.
           * @param a the Authentication
           */
          public ExtensibleMethodSecurityExpressionRoot(final Authentication a) {
              super(a);
          }    
      }
      

      我的完整方法是这样的: ExtensibleMethodSecurityExpressionHandler 更改评估根上下文:

      package org.springframework.security.access.expression.method;
      
      import java.lang.reflect.Array;
      import java.util.ArrayList;
      import java.util.Collection;
      import java.util.List;
      
      import org.aopalliance.intercept.MethodInvocation;
      import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
      import org.springframework.core.ParameterNameDiscoverer;
      import org.springframework.expression.EvaluationContext;
      import org.springframework.expression.Expression;
      import org.springframework.expression.ExpressionParser;
      import org.springframework.expression.spel.standard.SpelExpressionParser;
      import org.springframework.security.access.PermissionEvaluator;
      import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
      import org.springframework.security.access.expression.ExpressionUtils;
      import org.springframework.security.access.expression.method.defaultexpression.DefaultMethodSecuritiyExpressionRootFactory;
      import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
      import org.springframework.security.authentication.AuthenticationTrustResolver;
      import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
      import org.springframework.security.core.Authentication;
      
      /**
       * This class is the same like {@link MethodSecurityExpressionHandler} but its evaluation
       * root context can be exchanged.
       * To use an other evaluation root context, set an other {@link #methodSecurityExpRootFactory}.
       *
       */
      public class ExtensibleMethodSecurityExpressionHandler implements MethodSecurityExpressionHandler {
      
          /** The parameter name discoverer. */
          private ParameterNameDiscoverer parameterNameDiscoverer;
      
          /** The permission evaluator. */
          private PermissionEvaluator permissionEvaluator;
      
          /** The trust resolver. */
          private AuthenticationTrustResolver trustResolver;
      
          /** The expression parser. */
          private ExpressionParser expressionParser;
      
          /** The method security expression root factory. */
          private MethodSecurityExpressionRootFactory<?> methodSecurityExpRootFactory;
      
          /** The role hierarchy. */
          private RoleHierarchy roleHierarchy;
      
          /**
           * Instantiates a new extensible method security expression handler.
           */
          public ExtensibleMethodSecurityExpressionHandler() {
              this.parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
              this.permissionEvaluator = new DenyAllPermissionEvaluator();
              this.trustResolver = new AuthenticationTrustResolverImpl();
              this.expressionParser = new SpelExpressionParser();
              this.methodSecurityExpRootFactory = new DefaultMethodSecuritiyExpressionRootFactory();
          }
      
          /**
           * Uses a {@link MethodSecurityEvaluationContext} as the <tt>EvaluationContext</tt> implementation and
           * configures it with a {@link MethodSecurityExpressionRoot} instance as the expression root object.
           *
           * @param auth the auth
           * @param mi the mi
           * @return the evaluation context
           */
          @Override
          public EvaluationContext createEvaluationContext(final Authentication auth, final MethodInvocation mi) {
              MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth,
                      mi,
                      this.parameterNameDiscoverer);
              MethodSecurityExpressionRoot root = this.methodSecurityExpRootFactory.createMethodSecurityExpressionRoot(auth);
              root.setTrustResolver(this.trustResolver);
              root.setPermissionEvaluator(this.permissionEvaluator);
              root.setRoleHierarchy(this.roleHierarchy);
              ctx.setRootObject(root);
      
              return ctx;
          }
      
          /*
           * (non-Javadoc)
           * 
           * @see
           * org.springframework.security.access.expression.method.MethodSecurityExpressionHandler#filter(java.lang.Object,
           * org.springframework.expression.Expression, org.springframework.expression.EvaluationContext)
           */
          @Override
          @SuppressWarnings({ "unchecked", "rawtypes" })
          public Object filter(final Object filterTarget, final Expression filterExpression, final EvaluationContext ctx) {
              MethodSecurityExpressionRoot rootObject = (MethodSecurityExpressionRoot) ctx.getRootObject().getValue();
              List retainList;
      
              if (filterTarget instanceof Collection) {
                  Collection collection = (Collection) filterTarget;
                  retainList = new ArrayList(collection.size());
      
                  for (Object filterObject : (Collection) filterTarget) {
                      rootObject.setFilterObject(filterObject);
      
                      if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
                          retainList.add(filterObject);
                      }
                  }
      
                  collection.clear();
                  collection.addAll(retainList);
      
                  return filterTarget;
              }
      
              if (filterTarget.getClass().isArray()) {
                  Object[] array = (Object[]) filterTarget;
                  retainList = new ArrayList(array.length);
      
                  for (Object element : array) {
                      rootObject.setFilterObject(element);
      
                      if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
                          retainList.add(element);
                      }
                  }
      
                  Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass().getComponentType(),
                          retainList.size());
                  for (int i = 0; i < retainList.size(); i++) {
                      filtered[i] = retainList.get(i);
                  }
      
                  return filtered;
              }
      
              throw new IllegalArgumentException("Filter target must be a collection or array type, but was " + filterTarget);
          }
      
          /*
           * (non-Javadoc)
           * 
           * @see org.springframework.security.access.expression.method.MethodSecurityExpressionHandler#getExpressionParser()
           */
          @Override
          public ExpressionParser getExpressionParser() {
              return this.expressionParser;
          }
      
          /**
           * Sets the parameter name discoverer.
           *
           * @param parameterNameDiscoverer the new parameter name discoverer
           */
          public void setParameterNameDiscoverer(final ParameterNameDiscoverer parameterNameDiscoverer) {
              this.parameterNameDiscoverer = parameterNameDiscoverer;
          }
      
          /**
           * Sets the permission evaluator.
           *
           * @param permissionEvaluator the new permission evaluator
           */
          public void setPermissionEvaluator(final PermissionEvaluator permissionEvaluator) {
              this.permissionEvaluator = permissionEvaluator;
          }
      
          /**
           * Sets the trust resolver.
           *
           * @param trustResolver the new trust resolver
           */
          public void setTrustResolver(final AuthenticationTrustResolver trustResolver) {
              this.trustResolver = trustResolver;
          }
      
          /*
           * (non-Javadoc)
           * 
           * @see
           * org.springframework.security.access.expression.method.MethodSecurityExpressionHandler#setReturnObject(java.lang
           * .Object, org.springframework.expression.EvaluationContext)
           */
          @Override
          public void setReturnObject(final Object returnObject, final EvaluationContext ctx) {
              ((MethodSecurityExpressionRoot) ctx.getRootObject().getValue()).setReturnObject(returnObject);
          }
      
          /**
           * Sets the role hierarchy.
           *
           * @param roleHierarchy the new role hierarchy
           */
          public void setRoleHierarchy(final RoleHierarchy roleHierarchy) {
              this.roleHierarchy = roleHierarchy;
          }
      
          /**
           * Gets the method security expression root factory.
           *
           * @return the method security expression root factory
           */
          public MethodSecurityExpressionRootFactory<?> getMethodSecurityExpressionRootFactory() {
              return this.methodSecurityExpRootFactory;
          }
      
          /**
           * Sets the method security expression root factory.
           *
           * @param methodSecurityExpressionRootFactory the new method security expression root factory
           */
          public void setMethodSecurityExpressionRootFactory(
                  final MethodSecurityExpressionRootFactory<?> methodSecurityExpressionRootFactory) {
              this.methodSecurityExpRootFactory = methodSecurityExpressionRootFactory;
          }
      
          /**
           * Gets the parameter name discoverer.
           *
           * @return the parameter name discoverer
           */
          public ParameterNameDiscoverer getParameterNameDiscoverer() {
              return this.parameterNameDiscoverer;
          }
      
          /**
           * Gets the permission evaluator.
           *
           * @return the permission evaluator
           */
          public PermissionEvaluator getPermissionEvaluator() {
              return this.permissionEvaluator;
          }
      
          /**
           * Gets the trust resolver.
           *
           * @return the trust resolver
           */
          public AuthenticationTrustResolver getTrustResolver() {
              return this.trustResolver;
          }
      
          /**
           * Gets the role hierarchy.
           *
           * @return the role hierarchy
           */
          public RoleHierarchy getRoleHierarchy() {
              return this.roleHierarchy;
          }
      
          /**
           * Sets the expression parser.
           *
           * @param expressionParser the new expression parser
           */
          public void setExpressionParser(final ExpressionParser expressionParser) {
              this.expressionParser = expressionParser;
          }   
      }
      

      MethodSecurityExpressionRootFactory:

      package org.springframework.security.access.expression.method;
      
      import org.springframework.security.core.Authentication;
      
      /**
       * Factory Class/Template Class-Pattern: Template Class interface to create different expression root objects.
       *
       * @param <T> the {@link ExtensibleMethodSecurityExpressionRoot} created by this factory.
       */
      public interface MethodSecurityExpressionRootFactory<T extends ExtensibleMethodSecurityExpressionRoot> {
          /**
           * Creates a new MethodSecurityExpressionRoot object.
           *
           * @param authentication the authentication
           * @return the extensible method security expression root
           */
          T createMethodSecurityExpressionRoot(final Authentication authentication);
      }
      

      DefaultMethodSecuritiyExpressionRootFactory:仅当想要使用没有自己扩展的 ExtensibleMethodSecurityExpression 处理程序时才需要

      package org.springframework.security.access.expression.method.defaultexpression;
      import org.springframework.security.access.expression.method.ExtensibleMethodSecurityExpressionRoot;
      import org.springframework.security.access.expression.method.MethodSecurityExpressionRootFactory;
      import org.springframework.security.core.Authentication;
      
      /**
       * Create the default {@link ExtensibleMethodSecurityExpressionRoot} expression root.
       */
      public class DefaultMethodSecuritiyExpressionRootFactory implements
              MethodSecurityExpressionRootFactory<ExtensibleMethodSecurityExpressionRoot> {
      
          @Override
          public ExtensibleMethodSecurityExpressionRoot createMethodSecurityExpressionRoot(final Authentication auth) {
              return new ExtensibleMethodSecurityExpressionRoot(auth);
          }   
      }
      

      示例自定义方法表达式根

      package com.queomedia.vwcotool.infrastructure.security.spring;   
      import org.springframework.security.access.expression.method.ExtensibleMethodSecurityExpressionRoot;
      import org.springframework.security.core.Authentication;
      
      public class VwCoToolMethodSecurityExpressionRoot extends ExtensibleMethodSecurityExpressionRoot {
      
          private Authentication a;
      
          public MyMethodSecurityExpressionRoot(final Authentication a) {
              super(a);
              this.a = a;
          }
      
          public isXXX(final DomainObject x){
              return x.getCreator().getName().equals(a.getPrincipal());
          }
      }
      

      【讨论】:

      • 如果我没记错的话,这种添加新方法的方式不适用于较新的 Spring Security 版本。现在推荐的方法是覆盖 DefaultMethodSecurityExpressionHandler。
      猜你喜欢
      • 2015-01-01
      • 2018-07-05
      • 2015-01-01
      • 2014-12-14
      • 2018-06-26
      • 1970-01-01
      • 2011-11-08
      • 2021-10-31
      • 1970-01-01
      相关资源
      最近更新 更多