【问题标题】:How to evaluate a SpEL Security expression in custom java code?如何在自定义 Java 代码中评估 SpEL 安全表达式?
【发布时间】:2013-06-20 06:04:48
【问题描述】:

我需要发明一种新型注释,其中一个字段是 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 是本地包。甚至还有一个task about making it public in Spring Security JIRA 一年没有得到开发者关注。

即使它不是本地包,我仍然对从哪里获取 SecurityExpressionRoot 类的方法 setTrustResolversetRoleHierarchysetPermissionEvaluator 的对象知之甚少,这似乎是必需的功能正常。

所以,我的问题是:如何正确获取正确的 SecurityExpressionRoot-subclass 实例以及如何使用所需对象填充它?

【问题讨论】:

  • 你为什么不用DefaultMethodSecurityExpressionHandler?如果我可以问,您的用例有什么特别之处,您需要一个新的注释?
  • 感谢您指出DefaultMethodSecurityExpressionHandler 的方向,但您能否展示一下它应该用于的方式? MethodInvocation 是什么东西,在那儿传来传去?你如何使用它? --- 我的特殊用例是使用带注释的控制器构建导航菜单;在某些情况下,我希望根据 SpEL 安全表达式隐藏一些菜单项(例如,如果用户未通过身份验证或没有“个人资料”角色等,则不显示“我的个人资料”菜单项);
  • @Ralph,我想要的是在我自己的自定义代码中手动评估 SpEL 表达式,使用已经存在的SecurityExpressionRoot,而不是使用一些新功能扩展基本SecurityExpressionRoot,用于 Spring 内部某个地方的常规评估。
  • 如果你在 JSP 中构建菜单,你可以使用 Spring 的<security:authorize access="${menuItem.securityExpression}"> 标签让你的生活更轻松。

标签: spring spring-security spring-el


【解决方案1】:

我正在解决同样的问题。我有一个菜单项列表。每个菜单项都包含一个安全表达式字符串 (SpEl)。我尝试使用 @PostFilter("filterObject.securityExpression") 但我不知道如何评估 SpEl 字符串中的 SpEl 字符串。

所以我最终使用了自定义评估器 bean。深受 org.thymeleaf.extras.springsecurity4.auth.AuthUtils 的启发

评估器使用与 Web 安全过滤器相同的 SecurityExpressionHandler。这意味着有必要为评估上下文提供请求和响应。但这不应该很复杂,因为 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());
}

【讨论】:

    【解决方案2】:

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

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

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

    但这不会给你带来安全感,它只会让 gui 变得更好。服务器还需要通过以下方式保护:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-13
      • 2011-02-08
      • 1970-01-01
      • 1970-01-01
      • 2013-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多