5

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?

Alexander Tumin
  • 1,561
  • 4
  • 22
  • 33
  • Why don't you use `DefaultMethodSecurityExpressionHandler`? What is special about your use-case that you need a new annotation if I may ask? – Pavel Horal Jun 23 '13 at 20:08
  • Thanks for pointing in direction of `DefaultMethodSecurityExpressionHandler`, but may you show a way it supposed to be used in? What is `MethodInvocation` thing, passed around there? How do you use it? --- My special use-case is a building navigational menu out of annotated controllers; and in some cases i wish to hide some menu items, based on SpEL security expressions (like, not showing 'My Profile' menu item if user is not authenticated or not having 'PROFILE' role and such); – Alexander Tumin Jun 23 '13 at 21:35
  • Possible duplicate of http://stackoverflow.com/questions/6632982/how-to-create-custom-methods-for-use-in-spring-security-expression-language-anno – Ralph Jun 24 '13 at 00:42
  • @Ralph, what i want is to evaluate a SpEL expression by hand, in my own custom code, using already existing `SecurityExpressionRoot`'s and not to extend base `SecurityExpressionRoot` with some new functions, for usual evaluation somewhere inside Spring internals. – Alexander Tumin Jun 24 '13 at 06:29
  • If you are building the menu in JSP, you can make your life easier by using Spring's `` tag. – Pavel Horal Jun 24 '13 at 06:43
  • @AlexanderTumin did you ever solve this problem? I would like to do the same thing, I'm surprised it isn't easier – JBCP Jan 05 '15 at 22:44

2 Answers2

1

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());
}
Václav Kužel
  • 1,070
  • 13
  • 16
0

I managed to achieve exactly this without any new annotations. The first thing you need to do is wrap your menu item in a sec:authorize tag, where the sec namespace is from spring security taglibs. We use:

<sec:authorize access="hasRole('${menuItem.permission}')"></sec:authorzie>

where ${menuItem.permission} is the permission field of the current menuItem object (we're looping through menuItems that we've retrieved from the server). The SpEl hasRole() is implemented by spring in the org.springframework.security.access.expression.SecurityExpressionOperations class.

That won't give you security though, it'll just make the gui nice. The server also needs to be secured with something like this:

@PreAuthorize("hasRole('...')")

The @PreAuthorize annotation is also from spring security, and it stops a client from executing a method on your server unless the user has the given role. To make this work we had to implement the org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService. A similar class exists for most identity management servers. We also had to implement org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao, but we're using ldap too. YMMV.

Software Engineer
  • 15,457
  • 7
  • 74
  • 102