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