如何在自定义Java代码中评估SpEL安全表达式?

时间:2013-06-23 17:44:44

标签: spring spring-security spring-el

我需要发明一种新类型的注释,其中一个字段是Spring Expression Language(aka SpEL)表达式字符串。

经过一些谷歌搜索和检查现有的类,我已经发现评估表达式的方式可能就像这样(如果我错了,请纠正我):

    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

但问题是:最适合我的情况MethodSecurityExpressionRoot是package-local。甚至还有task about making it public in Spring Security JIRA一年没有得到开发人员的关注。

即使它不是本地包,我仍然不清楚在哪里获取setTrustResolversetRoleHierarchysetPermissionEvaluator SecurityExpressionRoot方法的对象这个类似乎是正常运作所必需的。

所以,我的问题是:你如何正确地获得正确的SecurityExpressionRoot - 子类实例以及如何用必需的对象填充它?

2 个答案:

答案 0 :(得分:0)

我设法在没有任何新注释的情况下实现了这一点。您需要做的第一件事是将菜单项包装在sec:authorize标记中,其中sec命名空间来自spring security taglibs。我们使用:

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

其中${menuItem.permission}是当前permission对象的menuItem字段(我们循环访问我们从服务器检索到的menuItems)。 SpEl hasRole()org.springframework.security.access.expression.SecurityExpressionOperations类中的spring实现。

这不会给你带来安全感,它只会使gui变得更好。服务器还需要使用以下内容进行保护:

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

@PreAuthorize注释也来自spring security,它会阻止客户端在服务器上执行方法,除非用户具有给定的角色。为了完成这项工作,我们必须实施org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService。大多数身份管理服务器都存在类似的类。我们还必须实现org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao,但我们也在使用ldap。 YMMV。

答案 1 :(得分:0)

我正在解决同样的问题。我有一个菜单项列表。每个菜单项都包含一个安全表达式字符串(SpEl)。我试图使用@PostFilter(“filterObject.securityExpression”),但我无法弄清楚如何评估SpEl字符串中的SpEl字符串。

所以我最终得到了自定义评估器bean。受到org.thymeleaf.extras.springsecurity4.auth.AuthUtils

的启发

评估者使用相同的SecurityExpressionHandler作为Web安全过滤器。这意味着必须为评估环境提供请求和响应。但这并不复杂,因为Spring将这些值注入控制器方法。

求值:

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

用作控制器方法:

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