I would like to secure my services layer using Spring Security. As explained in the documentation, I need to use a MethodSecurityInterceptor
that will check if the method invocation is allowed.
To decide if a service method invocation is allowed for a given user, affecting a required role to the invoked method (using MethodSecurityMetadataSource
) is not enough for me since it also depends on the parameters passed to the method. As suggested in the documentation, I can write a custom AccessDecisionVoter
and access the arguments though the secured object (MethodInvocation
in this case).
But, my authorization logic is different across the methods. For example, the arguments may be different between multiple methods and the authorization logic will also be different.
I see two options:
AccessDecisionVoter
to determine the invoked method and the authorization logic to use, but it seems to be an ugly solution.MethodSecurityInterceptor
per method to secure. According to the Spring documentation, a MethodSecurityInterceptor
is used to secure many methods, so it makes me thinking there is another way.The same question exists for access decision after method invocation (using AfterInvocationProvider
).
What are the alternatives?
The real difference is that @PreAuthorize can work with Spring Expression Language (SpEL). You can: Access methods and properties of SecurityExpressionRoot . (Advanced feature) Add your own methods (override MethodSecurityExpressionHandler and set it as <global-method-security><expression-handler ... /></...> ).
The @PreAuthorize annotation checks the given expression before entering the method, whereas the @PostAuthorize annotation verifies it after the execution of the method and could alter the result.
The Secured annotation is used to define a list of security configuration attributes for business methods. This annotation can be used as a Java 5 alternative to XML configuration.
Figure 16.1 Global method security enables you to apply authorization rules at any layer of your application. This approach allows you to be more granular and to apply authorization rules at a specifically chosen level.
I achieved that by implementing my own AccessDecisionManager
that delegates access decisions to my special interface AccessDecisionStrategy
:
public interface AccessDecisionStrategy {
void decide(Authentication authentication, MethodInvocation methodInvocation, ConfigAttribute configAttribute);
}
Each access decision strategy represents different way of making access decision.
You can easily implement your own strategy (even in other language - for instance Scala):
public class SomeStrategy implements AccessDecisionStrategy { ...
As you can see, my AccessDecisionManager
has a map of strategies. Strategy used by manager is based on annotation argument.
public class MethodSecurityAccessDecisionManager implements AccessDecisionManager {
private Map<String, AccessDecisionStrategy> strategyMap;
public MethodSecurityAccessDecisionManager(Map<String, AccessDecisionStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
ConfigAttribute configAttribute = getSingleConfigAttribute(configAttributes);
AccessDecisionStrategy accessDecisionStrategy = strategyMap.get(configAttribute.getAttribute());
if (accessDecisionStrategy == null) {
throw new IllegalStateException("AccessDecisionStrategy with name "
+ configAttribute.getAttribute() + " was not found!");
}
try {
accessDecisionStrategy.decide(authentication, (MethodInvocation) object, configAttribute);
} catch (ClassCastException e) {
throw new IllegalStateException();
}
}
private ConfigAttribute getSingleConfigAttribute(Collection<ConfigAttribute> configAttributes) {
if (configAttributes == null || configAttributes.size() != 1) {
throw new IllegalStateException("Invalid config attribute configuration");
}
return configAttributes.iterator().next();
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return clazz.equals(MethodInvocation.class);
}
}
Now when I want to protect my method I put @Secured
annotation with argument that is name of the strategy:
@Secured("GetByOwner")
FlightSpotting getFlightSpotting(Long id);
You can implement and configure as many strategies as you want:
<bean id="methodSecurityAccessDecisionManager"
class="some.package.MethodSecurityAccessDecisionManager">
<constructor-arg>
<map>
<entry key="GetByOwner">
<bean class="some.package.GetByOwnerStrategy"/>
</entry>
<entry key="SomeOther">
<bean class="some.package.SomeOtherStrategy"/>
</entry>
</map>
</constructor-arg>
</bean>
To inject that access decision manager you type:
<sec:global-method-security secured-annotations="enabled"
access-decision-manager-ref="methodSecurityAccessDecisionManager">
</sec:global-method-security>
I also implemented helper class to handle MethodInvocation
arguments:
import org.aopalliance.intercept.MethodInvocation;
public class MethodInvocationExtractor<ArgumentType> {
private MethodInvocation methodInvocation;
public MethodInvocationExtractor(MethodInvocation methodInvocation) {
this.methodInvocation = methodInvocation;
}
public ArgumentType getArg(int num) {
try {
Object[] arguments = methodInvocation.getArguments();
return (ArgumentType) arguments[num];
} catch (ClassCastException | ArrayIndexOutOfBoundsException e) {
throw new IllegalStateException();
}
}
}
Now you can easily extract interesting arguments in the code of your strategy to make decision:
Let's say I want to get argument number 0
that is of type Long
:
MethodInvocationExtractor<Long> extractor = new MethodInvocationExtractor<>(methodInvocation);
Long id = extractor.getArg(0);
You can implement your own method security annotations based on Spring @PreAuthorize("")
construction.
To fetch extra information about the method(beyond method argument values) to SpEL evaluation context you can implement your own MethodSecurityExpressionHandler
@Service
public class MySecurityExpressionHandler extends
DefaultMethodSecurityExpressionHandler {
@Override
public StandardEvaluationContext createEvaluationContextInternal(
Authentication auth, MethodInvocation mi) {
StandardEvaluationContext evaluationContext = super
.createEvaluationContextInternal(auth, mi);
SomeMethodInfoData methodInfoData = mi.getMethod(). ...;
evaluationContext.setVariable("someData", <value computed based on method info data>);
}
return evaluationContext;
}
and register it in your global-method-security
declaration
<security:global-method-security
pre-post-annotations="enabled">
<security:expression-handler
ref="mySecurityExpressionHandler" />
</security:global-method-security>
Now you can create custom security annotations(and extra process annotation data in MySecurityExpressionHandler if required)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("#<someData>")
public @interface CustomSecurityAnnotation { ... }
for example you can create a custom annotation to check user roles without messing with strings:
@MyUserRoleCheck(MyAppRole.Admin)
public void someMethod()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With