FC
FACADE
CODE
Lesson 17 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

Domain Object Instance Security

Remember we assigned PLAY_COURSE and UPDATE_COURSE permissions to the STUDENT and INSTRUCTOR roles respectively. It allows any Students to play any course, similarly any Instructors can update any course. But our objective is to

  1. Play only the courses enrolled by the Student.
  2. Update only the courses created by the Instructor.

In order to achieve this we need to enhance the security of these two service methods in addition to the permissions assigned. Obviously we can implement the authorization checks in ServiceSecurity bean similar to what we did in the previous chapter for ViewProfile:


// PLAY_COURSE permission - Check if the course is enrolled by the authenticated user
public boolean isEnrolledStudent(Authentication authentication, Long courseId) {
    Optional<AppUser> student = appUserRepository.findByUsername(authentication.getName());
    if (student.isPresent()) {
        return student.get()
                .getEnrolledCourses()
                .stream()
                .anyMatch(course -> course.getId().equals(courseId));
    }
    return false;
}

// UPDATE_COURSE permission - Check if the course is created by the authenticated user
public boolean isCreatedBy(Authentication authentication, Long courseId) {
    Optional<Course> course = courseRepository.findById(courseId);
    if (course.isPresent()) {
        return course.get()
              .getCreatedBy()
              .getUsername()
              .equalsIgnoreCase(authentication.getName());
    }
    return false;
}

hasPermission() - SpEL Expression

So far we have covered Web Request Security and Service Layer Security, but what we are dealing now is all about securing the domain object instances i.e., Who can do what on a specific Course? And it requires a different approach.

Similar to hasRole() and hasAuthority(), Spring Security offers below built-in SpEL expressions to achieve domain object instance security.


hasPermission(Object targetDomainObject, Object permission);

hasPermission(Serializable targetId, String targetType, Object permission);

Update Authority

Let's replace hasAuthority() with hasPermission() expression for PLAY_COURSE and UPDATE_COURSE constants in Authority class.


public class Authority {
  // Other constants omitted for brevity
  private static final String PLAY_COURSE = "hasPermission(#courseId, T(com.thecodefacts.spring.security.domain.Course).getSimpleName(), T(com.thecodefacts.spring.security.enums.PermissionEnum).PLAY_COURSE.name())";
  
  private static final String UPDATE_COURSE = "hasPermission(#courseId, T(com.thecodefacts.spring.security.domain.Course).getSimpleName(), T(com.thecodefacts.spring.security.enums.PermissionEnum).UPDATE_COURSE.name())";
}

Here the first parameter #courseId corresponds to the argument with the same name defined in the corresponding service methods. And it represents the unique ID of the Course object. Second parameter represents the type of the target domain object. Third parameter represents the permission required to authorize the service method execution.

With hasRole() and hasAuthority() expressions, Spring Security automatically verified if the authenticated user has the given role or permission. But with hasPermission() expression, we can ask below two questions:

  1. Who will implement the authorization check for the hasPermission() SpEL expression?
  2. How do we get the authenticated user details to check for the given permission?

PermissionEvaluator

hasPermission() expressions are basically delegated to an instance of PermissionEvaluator interface, which has two methods:


boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission);

boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission);

These methods map directly to the hasPermission() SpEL expression with the exception that the first argument Authentication object will be passed automatically by Spring Security.

PermissionEvaluator interface is intended to be the bridge between Spring's SpEL expression system and Spring Security's ACL System to implement domain object instance security. But it does not ship with standard Spring Security framework, we need to manually add spring-security-acl-xxx.jar as mvn dependency in pom.xml.

What is Spring Security's ACL?

Access Control List (ACL) looks similar to the Security tab you can find in the Windows system in file properties for each file resource. It maintains the list of users and their permissions on the respective file resource.

Similarly Spring Security's ACL system works based on the permissions granted for each user on each domain object instances. This ACL information is stored in the database which will be used exclusively by spring-security-acl-xxx.jar.

Downside of ACL

Spring Security's ACL is great and easier to write but it does not scale well for some of the following reasons.

  1. Assume an application having 1 Million object instances, 1000 users and a minimum of four basic actions (CRUD) = 4 Billion ACL records. Imagine a query executing on these many records for each Service method invocation. This can potentially cause performance issues and can become a maintenance nightmare.
  2. ACL records are normally stored with the ID of the subject and the object together with the permissions in the database table. So ACL systems are basically driven by the identifiers of the subject and the object, not by their attributes. This in itself have few practical issues listed below:
  1. We can't understand from the ACL table who can do what on which domain object instances.
  2. It is also nearly impossible to understand the rules driving the granted permissions.
  3. Any change in the attributes of the subject or object requires constant updation of these ACL records.
  4. Any change in the requirement change in terms of granted permissions require a huge insert/update/deletion of these ACL records corresponding to the impacted subject associated with the object under authorization.

Attribute-based Access Control (ABAC)

The drawbacks of ACL can be overcome by Attribute-based access control (ABAC) or Policy-based access control (PBAC). ABAC is different and a recently popular authorization mechanism to secure the domain object instances.

XACML is a specification to define and evaluate such attribute-based access control policies. But we are not going to cover XACML here, we will then be deviating from Spring Security. If all your access control needs can be implemented by simple patterns and conditions within the context of your domain model, specifications like XACML will be an overkill for small and medium-sized applications.

Let's see how we are going to implement ABAC with Spring Security's PermissionEvaluator interface in the coming chapters.