【问题标题】:How to create custom methods for use in spring security expression language annotations如何创建自定义方法以在 Spring 安全表达式语言注释中使用
【发布时间】:2011-10-01 18:10:16
【问题描述】:

我想创建一个添加自定义方法的类,以便在 Spring 安全表达式语言中通过注释进行基于方法的授权。

例如,我想创建一个像“customMethodReturningBoolean”这样的自定义方法,以便像这样使用:

  @PreAuthorize("customMethodReturningBoolean()")
  public void myMethodToSecure() { 
    // whatever
  }

我的问题是这样的。 如果可能的话,我应该继承什么类来创建我的自定义方法,我将如何在 spring xml 配置文件中配置它,有人给我一个以这​​种方式使用的自定义方法的示例?

【问题讨论】:

  • 我现在没有时间输入答案,但我按照本指南进行操作,效果非常好:baeldung.com/…我使用的是 Spring Security 5.1.1。

标签: java spring spring-security


【解决方案1】:

上述技术均不再适用。似乎 Spring 已经竭尽全力阻止用户覆盖 SecurityExpressionRoot。

编辑 11/19/14 设置 Spring 以使用安全注释:

<beans ... xmlns:sec="http://www.springframework.org/schema/security" ... >
...
<sec:global-method-security pre-post-annotations="enabled" />

像这样创建一个 bean:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(String key) {
        return true;
    }
}

然后在你的jsp中做这样的事情:

<sec:authorize access="@mySecurityService.hasPermission('special')">
    <input type="button" value="Special Button" />
</sec:authorize>

或者注解一个方法:

@PreAuthorize("@mySecurityService.hasPermission('special')")
public void doSpecialStuff() { ... }

此外,您可以在 @PreAuthorize 注释中使用 Spring Expression Language 来访问当前身份验证以及方法参数。

例如:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, String foo) { ... }
}

然后更新您的@PreAuthorize 以匹配新方法签名:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }

【讨论】:

  • @Bosh 在你的 hasPermission 方法中,你可以使用Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 来获取当前的认证令牌。
  • 感谢詹姆斯的回答。我必须在 spring 配置文件中定义 mySecurityService 吗?
  • 如果您对服务所在的包进行了组件扫描设置,则无需在任何 XML 文件中定义 mySecurityService。如果您没有匹配的组件扫描,则您必须使用 xml bean 定义。 @PreAuthorize 来自 org.springframework.security
  • 您可能需要像这样为注解指定 bean 的名称:@Component("mySecurityService") 或使用 @Named 注解。
  • @VJS 请查看我所做的编辑。您将需要配置 spring 以使用这些注释。我很惊讶没有其他人抱怨这个重要的缺失细节:)
【解决方案2】:

感谢ericacm,但由于以下几个原因它不起作用:

  • DefaultMethodSecurityExpressionHandler 的属性是私有的(反射可见性不受欢迎)
  • 至少在我的 Eclipse 中,我无法解析 MethodSecurityEvaluationContext 对象

不同之处在于我们调用现有的createEvaluationContext 方法,然后添加我们的自定义根对象。最后我只返回了一个 StandardEvaluationContext 对象类型,因为 MethodSecurityEvaluationContext 不会在编译器中解析(它们都来自同一个接口)。这是我现在在生产中的代码。

MethodSecurityExpressionHandler 使用我们的自定义根:

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler  {

    // parent constructor
    public CustomMethodSecurityExpressionHandler() {
        super();
    }

    /**
     * Custom override to use {@link CustomSecurityExpressionRoot}
     * 
     * Uses a {@link MethodSecurityEvaluationContext} as the <tt>EvaluationContext</tt> implementation and
     * configures it with a {@link MethodSecurityExpressionRoot} instance as the expression root object.
     */
    @Override
    public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
        // due to private methods, call original method, then override it's root with ours
        StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(auth, mi);
        ctx.setRootObject( new CustomSecurityExpressionRoot(auth) );
        return ctx;
    }
}

这将通过扩展 SecurityExpressionRoot 替换默认根。这里我将 hasRole 重命名为 hasEntitlement:

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot  {

    // parent constructor
    public CustomSecurityExpressionRoot(Authentication a) {
        super(a);
    }

    /**
     * Pass through to hasRole preserving Entitlement method naming convention
     * @param expression
     * @return boolean
     */
    public boolean hasEntitlement(String expression) {
        return hasRole(expression);
    }

}

最后更新 securityContext.xml(并确保它是从您的 applcationContext.xml 中引用的):

<!-- setup method level security using annotations -->
<security:global-method-security
        jsr250-annotations="disabled"
        secured-annotations="disabled"
        pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<!--<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">-->
<bean id="expressionHandler" class="com.yourSite.security.CustomMethodSecurityExpressionHandler" />

注意:@Secured 注释将不接受此覆盖,因为它通过不同的验证处理程序运行。因此,在上面的 xml 中,我禁用了它们以防止以后混淆。

【讨论】:

    【解决方案3】:

    您需要继承两个类。

    首先,设置一个新的方法表达式处理程序

    <global-method-security>
      <expression-handler ref="myMethodSecurityExpressionHandler"/>
    </global-method-security>
    

    myMethodSecurityExpressionHandler 将是DefaultMethodSecurityExpressionHandler 的子类,它会覆盖createEvaluationContext(),在MethodSecurityEvaluationContext 上设置MethodSecurityExpressionRoot 的子类。

    例如:

    @Override
    public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
        MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, parameterNameDiscoverer);
        MethodSecurityExpressionRoot root = new MyMethodSecurityExpressionRoot(auth);
        root.setTrustResolver(trustResolver);
        root.setPermissionEvaluator(permissionEvaluator);
        root.setRoleHierarchy(roleHierarchy);
        ctx.setRootObject(root);
    
        return ctx;
    }
    

    【讨论】:

    • 嗯,听起来不错,但是 DefaultMethodSecurityExpressionHandler 的所有属性都是私有的,没有访问器,所以我很好奇你是如何在没有任何丑陋反射的情况下扩展类的。谢谢。
    • 你的意思是trustResolver等?这些都在 DefaultMethodSecurityExpressionHandler 中有设置器(至少在 Spring Security 3.0 中)见:static.springsource.org/spring-security/site/apidocs/org/…
    • @ericacm 你如何绕过MethodSecurityExpressionRoot 成为package-private
    猜你喜欢
    • 2017-01-11
    • 1970-01-01
    • 2019-02-20
    • 2013-07-22
    • 2015-12-31
    • 1970-01-01
    • 2016-05-18
    • 1970-01-01
    相关资源
    最近更新 更多