如何创建用于春季安全表达式语言注释的自定义方法

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

例如,我想创建一个像“ customMethodReturn ningBoolean”这样的自定义方法,以某种方式使用,如下所示:

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

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

70613 次浏览

You'll need to subclass two classes.

First, set a new method expression handler

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

myMethodSecurityExpressionHandler will be a subclass of DefaultMethodSecurityExpressionHandler which overrides createEvaluationContext(), setting a subclass of MethodSecurityExpressionRoot on the MethodSecurityEvaluationContext.

For example:

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

Thanks ericacm, but it does not work for a few reasons:

  • The properties of DefaultMethodSecurityExpressionHandler are private (reflection visibility kludges undesirable)
  • At least in my Eclipse, I can't resolve a MethodSecurityEvaluationContext object

The differences are that we call the existing createEvaluationContext method and then add our custom root object. Finally I just returned an StandardEvaluationContext object type since MethodSecurityEvaluationContext would not resolve in the compiler (they are both from the same interface). This is the code that I now have in production.

Make MethodSecurityExpressionHandler use our custom root:

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

This replaces the default root by extending SecurityExpressionRoot. Here I've renamed hasRole to 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);
}


}

Finally update securityContext.xml (and make sure it's referenced from your 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" />

Note: the @Secured annotation will not accept this override as it runs through a different validation handler. So, in the above xml I disabled them to prevent later confusion.

None of the mentioned techniques will work anymore. It seems as though Spring has gone through great lengths to prevent users from overriding the SecurityExpressionRoot.

EDIT 11/19/14 Setup Spring to use security annotations:

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

Create a bean like this:

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

Then do something like this in your jsp:

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

Or annotate a method:

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

Additionally, you may use Spring Expression Language in your @PreAuthorize annotations to access the current authentication as well as method arguments.

For example:

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

Then update your @PreAuthorize to match the new method signature:

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