如何创建自定义方法以在Spring安全表达式语言注释中使用

98

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

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

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

我的问题是:如果可能的话,我应该继承哪个类来创建自定义方法?我该如何在Spring XML配置文件中进行配置?能否给我一个使用自定义方法的示例?


3
我现在没有时间打字回答,但是我遵循了这个指南,它非常有效:https://www.baeldung.com/spring-security-create-new-custom-security-expression 我正在使用Spring Security 5.1.1。 - Paul
3个回答

186

现有的技术手段都已经不再有效了。似乎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表达式语言来访问当前的身份验证和方法参数。
例如:
@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, String foo) { ... }
}

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

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

7
在您的 hasPermission 方法中,您可以使用 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 来获取当前的身份验证令牌。 - James Watkins
2
谢谢詹姆斯的回答。我需要在Spring配置文件中定义mySecurityService吗? - WowBow
2
如果你在服务所在的包中有一个component-scan设置,你就不需要在任何XML文件中定义mySecurityService。如果你没有匹配的component-scan,则必须使用xml bean定义。@PreAuthorize来自org.springframework.security。 - James Watkins
3
你可能需要像这样在注释中指定 bean 的名称:@Component("mySecurityService"),或者使用 @Named 注释。 - James Watkins
2
@VJS,请查看我所做的修改。您需要配置Spring以使用这些注释。我很惊讶没有其他人抱怨这个重要的遗漏细节 :) - James Watkins
显示剩余14条评论

36

你需要继承两个类。

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

<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的所有属性都是私有的,没有访问器,所以我很好奇你如何扩展这个类而不使用任何丑陋的反射。谢谢。 - Joseph Lust
1
你是指 trustResolver 等吗?在默认的 DefaultMethodSecurityExpressionHandler 中,这些都有 setter 方法(至少在 Spring Security 3.0 中是这样)。请参见:http://static.springsource.org/spring-security/site/apidocs/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandler.html - sourcedelica
4
你如何绕过MethodSecurityExpressionRoot包私有的限制? - C. Ross

15

感谢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中,我禁用了它们以防止后续混淆。


谢谢。这对我非常有帮助,但是在Spring Security的新更改中,您不能覆盖_createEvaluationContext_,而必须覆盖_createEvaluationContextInternal_和_createSecurityExpressionRoot_。 - Hesam Karimian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接