Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to evaluate a SpEL Security expression in custom java code?

i have come to need to invent a new type of annotations, one of fields of which would be a Spring Expression Language (aka SpEL) expression string.

After a bit googling and examining existing classes, i've figured out that the way of evaluating expression might be like this one (correct me if i am wrong in any way):

    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("isAnonymous()"); // well, this is only an example
    SecurityExpressionRoot context = ... obtaining the instance of subclass of SecurityExpressionRoot ...
    System.out.println(exp.getValue(context)); // just an example

But here is the problem: the most suiting for my case MethodSecurityExpressionRoot is package-local. There is even a task about making it public in Spring Security JIRA which didn't got any attention from developers for a year.

And even if it wasn't package-local, i still have a weak understanding of where to obtain objects for methods setTrustResolver, setRoleHierarchy and setPermissionEvaluator of SecurityExpressionRoot class, which seems to be needed for it's proper functioning.

So, my question is: how do you properly get the correct SecurityExpressionRoot-subclass instance and how to populate it with required objects?

like image 702
Alexander Tumin Avatar asked Nov 13 '22 02:11

Alexander Tumin


1 Answers

I am solving same problem. I have a list of menu items. Each menu item contains a security expression string (SpEl). I tried to use @PostFilter("filterObject.securityExpression") but I couldn't figure out how to evaluate a SpEl string inside a SpEl string.

So I ended up with custom evaluator bean. Heavily inspired by org.thymeleaf.extras.springsecurity4.auth.AuthUtils

The evaluator uses same SecurityExpressionHandler as web security filters. This means its necessary to provide request and response for an evaluation context. But this shouldn't be complicated since Spring injects those values into controller methods.

Evaluator:

@Component
public class WebSecurityExpressionEvaluator {

    private static final FilterChain EMPTY_CHAIN = (request, response) -> {
        throw new UnsupportedOperationException();
    };

    private final List<SecurityExpressionHandler> securityExpressionHandlers;

    public WebSecurityExpressionEvaluator(List<SecurityExpressionHandler> securityExpressionHandlers) {
        this.securityExpressionHandlers = securityExpressionHandlers;
    }

    public boolean evaluate(String securityExpression, HttpServletRequest request, HttpServletResponse response) {
        SecurityExpressionHandler handler = getFilterSecurityHandler();

        Expression expression = handler.getExpressionParser().parseExpression(securityExpression);

        EvaluationContext evaluationContext = createEvaluationContext(handler, request, response);

        return ExpressionUtils.evaluateAsBoolean(expression, evaluationContext);
    }

    @SuppressWarnings("unchecked")
    private EvaluationContext createEvaluationContext(SecurityExpressionHandler handler, HttpServletRequest request, HttpServletResponse response) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        FilterInvocation filterInvocation = new FilterInvocation(request, response, EMPTY_CHAIN);

        return handler.createEvaluationContext(authentication, filterInvocation);
    }

    private SecurityExpressionHandler getFilterSecurityHandler() {
        return securityExpressionHandlers.stream()
                .filter(handler -> FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(handler.getClass(), SecurityExpressionHandler.class)))
                .findAny()
                .orElseThrow(() -> new IllegalStateException("No filter invocation security expression handler has been found! Handlers: " + securityExpressionHandlers.size()));
    }
}

Usage as a controller method:

@ModelAttribute("adminMenuItems")
public List<AdminMenuItem> getMenuItems(HttpServletRequest request, HttpServletResponse response) {
    List<AdminMenuItem> menuItems = ...
    return menuItems.stream().filter(item -> evaluator.evaluate(item.getSecurityExpression(), request, response)).collect(toList());
}
like image 164
Václav Kužel Avatar answered Nov 15 '22 05:11

Václav Kužel