Jithin M V
Jithin M V

Reputation: 393

Can anyone give an insight to how the @PreAuthorize annotation works?

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

Answers (4)

saba
saba

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

chvndb
chvndb

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

M. Deinum
M. Deinum

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

Tomas F
Tomas F

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

Related Questions