How to Create Custom Methods for Use in Spring Security Expression Language Annotations

How to create custom methods for use in spring security expression language annotations

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

Best way to create custom method security expression

Advantages of @PreAuthorize('isOwner(#someEntity)') way over @bean.method() way:

  • From maintenance point of view: when you change signature of some method like CustomSecurityExpressionRoot.isOwner() then it is clear for you (and even for some new developer familiar with Spring Security) that you need to review all @Pre / @Post annotations. This advantage is not so important if you have JUnit tests for all @Pre / @Post cases.
  • Short syntax (you can try some short alias to improve @bean.method() way, for example @sec.isOwner())
  • With SecurityExpressionRoot you automatically have access to authentication, trustResolver, roles, permissionEvaluator ojects. It is not so important because you can easy get them in your custom bean too.

Advantages of @bean.method() way over @PreAuthorize('isOwner(#someEntity)') way:

  • Easy setup

I am like your @bean.method() way. IMHO all differences are not so important (for my previous project). But I like "easy setup" option so much! So for next project I'll try your @bean.method() way in conjuction with JUnit tests for all @Pre / @Post cases.

How to use custom expressions in Spring Security @PreAuthorize/@PostAuthorize annotations

1) First you have to reimplement MethodSecurityExpressionRoot which contains extra method-specific functionality. The original Spring Security implementation is package private and hence it is not possible to just extend it. I suggest checking the source code for the given class.

public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

// copy everything from the original Spring Security MethodSecurityExpressionRoot

// add your custom methods

public boolean isAdmin() {
// do whatever you need to do, e.g. delegate to other components

// hint: you can here directly access Authentication object
// via inherited authentication field
}

public boolean isOwner(Long id) {
// do whatever you need to do, e.g. delegate to other components
}
}

2) Next you have to implement custom MethodSecurityExpressionHandler that will use the above defined CustomMethodSecurityExpressionRoot.

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

@Override
public void setReturnObject(Object returnObject, EvaluationContext ctx) {
((MethodSecurityExpressionRoot) ctx.getRootObject().getValue()).setReturnObject(returnObject);
}

@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
final CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());

return root;
}
}

3) Define expression handler bean in your context, e.g. via XML you can do it as follows

<bean id="methodSecurityExpressionHandler"
class="my.package.CustomMethodSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy" />
<property name="permissionEvaluator" ref="permissionEvaluator" />
</bean>

4) Register the above defined handler

<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="methodSecurityExpressionHandler"/>
</security:global-method-security>

5) Then just use the defined expressions in your @PreAuthorize and/or @PostAuthorize annotations

@PreAuthorize("isAdmin() or isOwner(#id)")
public void deleteGame(@PathVariable int id, @ModelAttribute currentGame) {
// do whatever needed
}

And one more thing. It is not very common to use method level security to secure controller methods but rather to secure methods with business logic (a.k.a. your service layer methods). Then you could use something like the below.

public interface GameService {

// rest omitted

@PreAuthorize("principal.admin or #game.owner = principal.username")
public void delete(@P("game") Game game);
}

But keep in mind that this is just an example. It expects that the actual principal has isAdmin() method and that the game has getOwner() method returning username of the owner.

How to create custom web security expression to use in JSP?

Here is what you need.
Follow below to create custom SpEL expression:

1) Create custom subclass of WebSecurityExpressionRoot class. In this subclass create a new method which you will use in expression. For example:

public class CustomWebSecurityExpressionRoot extends WebSecurityExpressionRoot {

public CustomWebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {
super(a, fi);
}

public boolean yourCustomMethod() {
boolean calculatedValue = ...;

return calculatedValue;

}
}

2) Create custom subclass of DefaultWebSecurityExpressionHandler class and override method createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) (not createEvaluationContext(...)) in it to return your CustomWebSecurityExpressionRoot instance. For example:

@Component(value="customExpressionHandler")
public class CustomWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {

@Override
protected SecurityExpressionRoot createSecurityExpressionRoot(
Authentication authentication, FilterInvocation fi) {

WebSecurityExpressionRoot expressionRoot = new CustomWebSecurityExpressionRoot(authentication, fi);

return expressionRoot;
}}

3) Define in your spring-security.xml the reference to your expression handler bean

<security:http access-denied-page="/error403.jsp" use-expressions="true" auto-config="false">
...

<security:expression-handler ref="customExpressionHandler"/>
</security:http>

After this, you can use your own custom expression instead of the standard one:

<security:authorize access="yourCustomMethod()">

Using custom method security annotation in spring security

You can extend GlobalMethodSecurityConfiguration in your configuration :

@EnableGlobalMethodSecurity
@Configuration
public class MyMethodSecurityConfig extends GlobalMethodSecurityConfiguration {

protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return SecuredAnnotationSecurityMetadataSource(...);
}
}

In xml, you can do :

<global-method-security metadata-source-ref="customMethodSecurityMetadataSource">
...
</global-method-security>
<bean id="customMethodSecurityMetadataSource" class="org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource">
...
</bean>

customMethodSecurityMetadataSource can be any instanceof MethodSecurityMetadataSource

How to create custom methods for use in spring security expression language annotations

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

How to evaluate a SpEL Security expression in custom java code?

I am solving same problem. I have a list of menu items. Each menu item contains a security expression string (SpEl). I tried to use @PostFilter("filterObject.securityExpression") but I couldn't figure out how to evaluate a SpEl string inside a SpEl string.

So I ended up with custom evaluator bean. Heavily inspired by org.thymeleaf.extras.springsecurity4.auth.AuthUtils

The evaluator uses same SecurityExpressionHandler as web security filters. This means its necessary to provide request and response for an evaluation context. But this shouldn't be complicated since Spring injects those values into controller methods.

Evaluator:

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

Usage as a controller method:

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


Related Topics



Leave a reply



Submit