Question

I am trying to create a a custom method for use in Pre/Post Authorize calls like this:

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;
    }
}

and

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);
    }
}

As seen above I have added the new method testDecision(String), which I can successfully use in my preAuthorize call as below:

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

But when I call it in the context of a PostAuthorize as:

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

I get a 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..)

Anyone can help me figure out what I am doing wrong?

Was it helpful?

Solution

It seems you are on an older version of Spring Security. As of Spring Security 3.1.5+ SEC-2245 is fixed & you can create your own expression root and implement MethodSecurityExpressionOperations.

OTHER TIPS

The class CustomSecurityExpressionRoot must extends MethodSecurityExpressionRoot! (implementing MethodSecurityExpressionOperations) is not enough.

Unfortunately MethodSecurityExpressionRoot is a package protected class.

  • Therfore you need to put CustomSecurityExpressionRoot in the same package (org.springframework.security.access.expression.method)
  • or you use the following class as super class for your CustomSecurityExpressionRoot (that is what I do in my projects)

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);
    }    
}

My complete way is this: ExtensibleMethodSecurityExpressionHandler to change the evaluation root context:

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: only needed if one want to use the ExtensibleMethodSecurityExpression handler without own extension

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);
    }   
}

Example Customized Method Expression Root

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());
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top