Learn Spring Security
Contents
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.