Reputation: 393
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
.
Upvotes: 14
Views: 20007
Reputation: 498
@PreAuthorize is equal to @Secured and @RolesAllowed.
@PreAuthorize("hasRole('ROLE_SPITTER')")
public void addSpittle(Spittle spittle) {
// ...
}
But the String argument to @PreAuthorize is a SpEL expression With SpEL expressions guiding access decisions, far more advanced security constraints can be written
@PreAuthorize(
"(hasRole('ROLE_SPITTER') and #spittle.text.length() <= 140)"
+"or hasRole('ROLE_PREMIUM')")
public void addSpittle(Spittle spittle) {
// ...
}
Upvotes: 2
Reputation: 745
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);
}
}
Upvotes: 23
Reputation: 125202
An annotation does nothing and is nothing more than metadata. Adding an annotation to a method and without a piece of code that acts on that annotation it does nothing. So you need an annotation processor of some sort.
For most of the annotations used in Spring this means they are applied by AOP (Aspect Oriented Programming). In Spring this means, by default, that is done based on a proxy. This proxy intercepts the actual method call, applies some additional logic before/after calling the actual method.
The additional logic is provided by a MethodInterceptor
or @Aspect
when using AspectJ based aspects. In the case of the @PreAuthorize
annotation this is done by the MethodSecurityInterceptor
.
What the MethodSecurityInterceptor
does is, based on the @PreAuthorize
annotation and the expression written in there (the expression is a SpEL expression) determine if the current user is authorized to access the method.
The MethodSescurityInterceptor
can be enabled either manually (doing the whole configuration yourself, quite tedious and error-prone) or by adding the @EnableGlobalMethodSecurifty
annotation. The latter will add an @Configuration
class and some processor to automatically configure the needed infrastructure classes.
Upvotes: 4
Reputation: 7596
An aspect invocation after evaluating the string given in the @PreAuthorize annotation.
For details I think you need to dig down in Spring source code. Maybe start around here: https://github.com/spring-projects/spring-security/blob/main/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java#nn
Upvotes: 0