FC
FACADE
CODE
Lesson 19 of 28
FC
FACADE
CODE

Learn Spring Security

Contents

01. Bootstrap The Application
1. Introduction
2. Install Spring Security
3. Enable Basic Auth
4. Authentication with AppUser
5. Password Encoder
02. Web Layer Security - RBAC
6. Permit Public APIs
7. Role Based Authorization
8. Disable CSRF
9. Current Authenticated User
03. Web Layer Security - PBAC
10. Permission Based Authorization
11. Define Permissions
12. Assign Permissions
13. Remove Role Based Access
04. Service Layer Security
14. PreAuthorize
15. PostAuthorize
16. Authorize Using Spring Beans
05. Domain Object Instance Security
17. Domain Object Instance Security
18. PermissionEvaluator Interface
19. PermissionEvaluator Strategy
20. DB Backed UserDetailsService
06. Token Based Authentication
21. Basic Authentication Revisited
22. Generate Token
23. Persist Token
24. Verify Token
25. Invalidate Token
07. Token Based Authentication and Authorization
26. JSON Web Token
27. Generate JWT
28. Verify JWT

PermissionEvaluator Strategy

As we can register only one PermissionEvaluator implementation, let's create a master implementation to delegate the authorization check to the appropriate PermissionEvaluator based on the type of the targetDomainObject. And we can register this master PermissionEvaluator with DefaultMethodSecurityExpressionHandler.

The above idea aligns with the Strategy Pattern which allows us to define a family of algorithms or strategies and choose one among them at run time. In our case the strategies are the various PermissionEvaluator implementations for each domain class.

Strategy Interface

Our first step is to identify all the strategies through a common interface. Let's create a Strategy interface extending PermissionEvaluator interface. And then we will update each strategies i.e., CoursePermissionEvaluator and AppUserPermissionEvaluator to implement the Strategy interface.


public interface PermissionEvaluatorStrategy<T> extends PermissionEvaluator {
    <T> Class<T> getTargetType();
}

@Component
public class CoursePermissionEvaluator implements PermissionEvaluatorStrategy<Course> {
    // hasPermission() overloaded methods omitted for brevity

    @Override
    public Class<Course> getTargetType() {
        return Course.class;
    }
}

@Component
public class AppUserPermissionEvaluator implements PermissionEvaluatorStrategy<AppUser> {
    // hasPermission() overloaded methods omitted for brevity

    @Override
    public Class<AppUser> getTargetType() {
        return AppUser.class;
    }
}

We created the Strategy interface with Generics where the parameterised type in each implementation class must represent the corresponding Entity class. It ensures each domain class to have its own PermissionEvaluatorStrategy implementation. And for our convenience we have added getTargetType() to return the Class Type of the domain class, in order to identify the right strategy based on the type of the targetDomainObject.

StrategyContext

Strategy Pattern suggests to let the Client choose one of the strategies and encapsulate it inside a StrategyContext class. The Client will then call the StrategyContext to delegate the execution to the encapsulated Strategy.

In our case we will use the master PermissionEvaluator to have the responsibilities of both Client and StrategyContext. Let's create PermissionEvaluatorStrategyContext class to encapsulate all the strategies, choose one among them at runtime and delegate the authorization check to the chosen strategy.


@Component
public class PermissionEvaluatorStrategyContext implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        if (targetDomainObject != null) {
            String targetType = targetDomainObject.getClass().getSimpleName();
            if (hasAuthority(authentication, permission)) {
                PermissionEvaluator permissionEvaluatorDelegate = getPermissionEvaluator(targetType);
                return permissionEvaluatorDelegate.hasPermission(authentication, targetDomainObject, permission);
            }
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        if (targetId != null) {
            if (hasAuthority(authentication, permission)) {
                PermissionEvaluator permissionEvaluatorDelegate = getPermissionEvaluator(targetType);
                return permissionEvaluatorDelegate.hasPermission(authentication, targetId, targetType, permission);
            }
        }
        return false;
    }
}

Here we are trying to find the appropriate PermissionEvaluator based on the type of the targetDomainObject and delegate the corresponding hasPermission() execution to it. In the first method we are finding it based on the fully qualified class name of the Entity retrieved from the targetDomainObject. Whereas in the second method we are finding it based on the same fully qualified class name of the Entity from the hasPermission() SpEL expression defined in Authority constants.

Find PermissionEvaluator Strategy

Now the master PermissionEvaluator is capable of delegating the authorization check based on the fully qualified class name of the Entity. We will encapsulate all the strategies by simply autowiring a list variable of type PermissionEvaluatorStrategy like below:


@Component
public class PermissionEvaluatorStrategyContext implements PermissionEvaluator {
    // hasPermission() overloaded methods omitted for brevity

    @Autowired
    private List<PermissionEvaluatorStrategy> strategies;

    public PermissionEvaluator getPermissionEvaluator(String name) {
        return strategies.stream()
            .filter(strategy ->
                strategy.getTargetType()
                    .getSimpleName()
                    .equalsIgnoreCase(name)
            )
            .findFirst()
            .orElseThrow(() -> new RuntimeException(String.format("No permission evaluator found for the class %s", name)));
    }
}

We will use getTargetType() method in each strategy to get the fully qualified name of the corresponding Entity. We will then use it to compare it against the argument to find the right strategy. Finally register PermissionEvaluatorStrategyContext class with DefaultMethodSecurityExpressionHandler in SecurityConfig configuration.

Benefits

By implementing Domain Object Instance Security using hasPermission() SpEL expression and PermissionEvaluator we have achieved more fine-grained access control on each domain object instances based on the attributes of the subject and the object. This is a simple and efficient solution to achieve attribute-based access control using Spring's built-in SpEL capability.

PermissionEvaluator implementations created for each domain class enable us to understand who are authorized to do what on them. It is easier to manage the permissions with PermissionEvaluator as it owns the permission related to the corresponding domain class defined in the parameterized type.

In a Microservice architecture each microservice owns only a minor subset of the larger Business domain. And the team who manages each microservice owns the resposibility to secure these domain object instances. Those who do not want to centralize and externalize the authorization policies can define and implement their own domain object permissions using PermissionEvaluator for each Entity.

What next?

So far we have covered various authorization techniques to secure different layers as mentioned below:

  1. Web request security using HttpSecurity configuration
  2. Service layer security using Method Security Annotations
  3. Domain object instance security using PermissionEvaluator

By this we have covered three out of the four security concerns to be addressed for any enterprise applications as suggested by Spring Security. One of the security concerns left to be covered is Authentication. Of course we enabled Basic Authentication at the start of the course and using it until now to authenticate the user. But there are a wide range of options available to implement Authentication. We will discuss some of the widely used authentication mechanisms using Spring Security in the upcoming chapters.

Before that let us replace InMemoryUserDetailsManager with our own UserDetailsService implementation in the next chapter.