I know Spring Security has an abstract class SecurityExpressionRoot. In that we have methods like hasAuthority(String var1), hasRole(String var1) etc implemented.
Spring also provide a @PreAuthorize annotation to be used on the method level we pass a single value within that annotation like
@PreAuthorize("hasRole('ROLE_ABC')")
The annotation @interface is like
package org.springframework.security.access.prepost;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface PreAuthorize {
String value();
}
I want to know how this annotation triggers the particular method from SecurityExpressionRoot.
Spring security uses Aspect Oriented Programming (AOP) to weave/intertwine security code into your own code base. The way this works in Spring is by using annotations that define injection points (cfr. pointcuts) to allow execution of extra logic before/after/within your own code (cfr. advice).
Interceptors scan your codebase for join points (i.e. for Spring this is always method execution when marked with specific annotations) and will execute the additional specific logic depending on which interception point (i.e. interface) you have used.
To enable this behaviour one could add a configuration:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class ConfigGlobalMethodSecurity extends GlobalMethodSecurityConfiguration {
...
}
For PreAuthorize in particular, the PrePostAdviceReactiveMethodInterceptor is responsible for finding your methods annotated with PreAutorize. In turn, this will delegate to PreInvocationAuthorizationAdvice which is configured here ReactiveMethodSecurityConfiguration as ExpressionBasedPreInvocationAdvice.
Internally this uses the default expression handler DefaultMethodSecurityExpressionHandler that creates a SecurityExpressionRoot. The actual implementation of this SecurityExpressionRoot will define how your expression within your PreAuthorize will be handled and what logic needs to be executed.
The SecurityExpressionRoot defines which expressions are allow within your PreAuthorize, such as hasRole.
To add additional expressions or extend upon the default permission logic you need to provide a custom implementation of SecurityExpressionRoot with optionally a custom PermissionEvaluator. E.g. if you would want to write @PreAuthorize("hasKnowledgeOf('AOP')").
public class CustomMethodSecurityExpressionRoot
extends SecurityExpressionRoot
implements MethodSecurityExpressionOperations {
private final PermissionEvaluator permissionEvaluator;
private final Authentication authentication;
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(
Authentication authentication,
PermissionEvaluator permissionEvaluator) {
super(authentication);
this.authentication = authentication;
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
// new expression to check if the requested knowledge is present
public boolean hasKnowledgeOf(String context) {
// provide logic that performs the check
}
@Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
@Override
public Object getFilterObject() {
return filterObject;
}
@Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public Object getReturnObject() {
return returnObject;
}
@Override
public Object getThis() {
return target;
}
}
and
@Configuration
public class CustomPermissionEvaluator
implements PermissionEvaluator {
@Override
public boolean hasPermission(
Authentication authentication,
Object targetDomainObject,
Object permission) {
// define your custom permission logic here
}
@Override
public boolean hasPermission(
Authentication authentication,
Serializable targetId,
String targetType,
Object permission) {
// define your custom permission logic here
}
}
To finish the configuration and pass the evaluator to the expression root.
public class CustomMethodSecurityExpressionHandler
extends DefaultMethodSecurityExpressionHandler {
PermissionEvaluator permissionEvaluator;
public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication,
MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(
authentication,
permissionEvaluator);
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
and
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class ConfigGlobalMethodSecurity extends GlobalMethodSecurityConfiguration {
@Autowired CustomPermissionEvaluator permissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new CustomMethodSecurityExpressionHandler(permissionEvaluator);
}
}
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