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

PreAuthorize

Service layer security

So far we have secured the API URLs using antMatchers() based on roles and permissions in HttpSecurity configuration. For few reasons discussed in this section, Spring Security recommends not to rely entirely on Web request security. Instead Security defined at the Service layer is much more robust and harder to bypass.

Service layer security is about securing the service methods which can be achieved using Spring Security's Method security annotations. As recommended by Spring Security, let's constrain ourselves using simple antPatterns in the Web request security, and move the permission based access model to the Service layer using @PreAuthorize annotation.

@PreAuthorize

@PreAuthorize annotation provides expression-based access control using SpEL (Spring Expression Language). It checks the given expression before entering the method. Some of the valid PreAuthorize annotations are:

  • @PreAuthorize("hasRole('ROLE_ADMIN')")
  • @PreAuthorize("hasAuthority('LIST_STUDENTS')")

Here the expression resembles the method we used along with antMatchers() in HttpSecurity configuration. We will use hasAuthority() expression with the appropriate PermissionEnum instances to secure the service methods by following the same permission-based access model like below:


@PreAuthorize("hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).PLAY_COURSE.name())")  
public Course play(Long courseId) {
}

SpEL expects the fully qualified name of the PermissionEnum instances. It may seem verbose but it is better to use Enums for type safety reasons rather than using String literals. As the SpEL expressions are validated on application startup, any typo mistake in the expression using enums will throw Exception.

SpEL expression constants

SpEL expression must always be a String constant. Let's move all the possible expressions in a constant class like below:


public class Authority {  
    public static final String LIST_STUDENTS = "hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).LIST_STUDENTS.name())";
    public static final String LIST_INSTRUCTORS = "hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).LIST_INSTRUCTORS.name())";
    public static final String VIEW_PROFILE = "hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).VIEW_PROFILE.name())";
    public static final String CREATE_COURSE = "hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).CREATE_COURSE.name())";
    public static final String UPDATE_COURSE = "hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).UPDATE_COURSE.name())";
    public static final String PLAY_COURSE = "hasAuthority(T(com.thecodefacts.spring.security.enums.PermissionEnum).PLAY_COURSE.name())";
}

Now we can simplify the above @PreAuthorize annotation using the constants like below:


@PreAuthorize(Authority.PLAY_COURSE)
public Course play(Long courseId) {
}

Refactor HttpSecurity

Since we have moved all the permission based access controls to the respective service methods, we no longer have to secure the corresponding REST APIs in HttpSecurity configuration. Also as recommended by Spring Security we will use "deny-by-default" approach using anyRequest().authenticated() and use simple ant paths to just permit the public APIs.

More importantly in order to enable method security annotations like PreAuthorize and PostAuthorize annotate ApiSecurityConfig with @EnableGlobalMethodSecurity with prePostEnabled value as true.


@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiSecurityConfig {
    @Bean
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests(auth -> auth
                .antMatchers(GET, PUBLIC_API_LIST).permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic();
        return http.build();
    }
}

There will not be any change in the Security behaviour with the shift from Web request security to Service layer security. All the authenticated requests will be handled by the APIs now, but only authorized requests makes into the Service method. Any unauthorized request to the Service method will be thrown back with 403 Forbidden error by the Method security annotation.

Note

You may often find developers using Method security annotations in Controller methods, though it is not restricted to do so. But Spring Security recommends applying them to the Service layer because Controller is simply the incorrect architectural layer to implement authorization decisions concerning services layer methods or domain object instances.