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

Define Permissions

Let's define the permissions for each API in an Enum in the format ACTION_RESOURCENAME as mentioned in the last chapter.


public enum PermissionEnum {
    CREATE_COURSE,
    UPDATE_COURSE,
    PLAY_COURSE,
    LIST_STUDENTS,
    LIST_INSTRUCTORS,
    VIEW_PROFILE
}

Secure the APIs with Permissions

We will add the remaining API urls in the SecurityConstants. We will then apply permission-based access restrictions to the UpdateCourse, PlayCourse and ViewProfile APIs in HttpSecurity configuration. Let's not do any changes in the existing role-based restrictions for now.


public static final String API_UPDATE_COURSES = "/api/v1/courses/*";
public static final String API_PLAY_COURSE = "/api/v1/courses/play/*";
public static final String API_VIEW_PROFILE = "/api/v1/users/*";  

@Bean
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests(auth -> auth
            .antMatchers(GET, PUBLIC_API_LIST).permitAll()
            // Role based access restrictions
            .antMatchers(API_LIST_STUDENTS, API_LIST_INSTRUCTORS).hasRole(ADMIN.name())
            .antMatchers(POST, API_CREATE_COURSES).hasRole(INSTRUCTOR.name())
            // Permission based access restrictions
            .antMatchers(API_UPDATE_COURSES).hasAuthority(UPDATE_COURSE.name())
            .antMatchers(API_PLAY_COURSE).hasAuthority(PLAY_COURSE.name())
            .antMatchers(API_VIEW_PROFILE).hasAuthority(VIEW_PROFILE.name())
            .anyRequest().authenticated()
        )
        .httpBasic();
    return http.build();
}

Before applying permission-based restrictions to the above three APIs, we were able to access them once authenticated successfully. But now we will get 403 Forbidden error for any user, as we have neither granted these Permissions to any Roles in the database nor updated the authorities in UserDetails.

AppPermission Entity

In order to assign permissions dynamically without changing the code, we have to model them as a database entity and establish the relationship with AppRole entity. As we can assign any number of permissions to any roles, we have to model this as many-to-many relationship with AppRole entity.

Let's create an Entity class for Permission and define a standard JPARepository interface for the entity.


@Entity(name = "app_permission")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class AppPermission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(value = EnumType.STRING)
    private PermissionEnum name;

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "permissions")
    @JsonIgnore
    private Set<AppRole> assignedRoles;
} 

@Repository
public interface AppPermissionRepository extends JpaRepository<AppPermission, Long> {
}

Update AppRole Entity

Now let's update the other side of the many-to-many relationship in AppRole entity, where AppRole entity will be the owner of the relation.


// Other details omitted for brevity
public class AppRole {
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "app_role_to_permission",
        joinColumns = @JoinColumn(name = "app_role_id"),
        inverseJoinColumns = @JoinColumn(name = "app_permission_id")
    )
    private Set<AppPermission> permissions;
}

Grant Permissions

The next step after modeling the entity is to create the permission records corresponding to each PermissionEnum instances. And then assign them to the roles as mentioned in the below table to align with the Security objectives defined in the first chapter. We can do this in AppDataInitialiser class in order to make these changes in the database while bootstraping the application.

RolePermissions
StudentPLAY_COURSE, VIEW_PROFILE
InstructorCREATE_COURSE, UPDATE_COURSE, PLAY_COURSE, VIEW_PROFILE
AdminLIST_STUDENTS, LIST_INSTRUCTORS, VIEW_PROFILE