This chapter focuses on the security measures for our application, emphasizing authentication and the protection of our business logic based on user roles.
To utilize Spring Security, it’s imperative to integrate security and its test module into our application. This can be achieved by adding the following XML configuration to the pom.xml
file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
</dependency>
Upon executing the application, accessing any endpoint should prompt the login screen. Additionally, a default user with the username user and a generated password can be found in the console. It’s important to refresh the Maven model structure if any issues arise.
-
Develop a configuration class named
SecurityConfiguration
. This class will house beans utilized during authentication and authorization. -
Define a bean
SecurityFilterChain
with the following specifications:-
Allow anonymous access to the endpoints /user and /h2-console/**.
-
Mandate authentication for all other endpoints.
-
Display an auto-generated user page when not authenticated.
-
Logout endpoint exists.
-
Additionally enable Basic Authentication to facilitate testing endpoints easily with Postman.
-
Disable CSRF (for h2-console and testing purposes) and configure frame options to be able to use h2-console.
-
.csrf(AbstractHttpConfigurer::disable)
.headers(customizer -> customizer
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
-
Establish a bean
UserDetailsService
with the following features:-
Operates in memory.
-
Configures test users with selected usernames and passwords.
-
-
Validate if the specified configurations function as intended.
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// Implement this
}
@Bean
public UserDetailsService userDetailsService() {
// Implement this
}
}
-
Test configuration You should be able to login as configured users when trying any of the protected endpoints via login form or Basic Authentication.
At the beginning, add annotation @EnableMethodSecurity to the security configuration class.
Please configure the following users:
-
admin - with the role ADMIN and MAINTAINER.
-
maintainer - with the role MAINTAINER.
-
user - without any role.
Now, you are prepared to safeguard your business logic. Grant access to specific user groups by employing the appropriate @PreAuthorize
annotation (since in the latest Spring version it is the one enabled by default) with the requisite permissions in your use case classes.
-
ADMIN can create, remove, and edit persons.
-
MAINTAINER can create, remove, and edit task lists and tasks.
-
Users without any assigned role are permitted to read any data.
It can be done using proper SpEL expression like:
@PreAuthorize("hasRole('ADMIN')")
If you want to, you can define static fields and use them in annotations like
@Component("Roles")
public class AccessControl {
public static String ROLE_ADMIN = "ADMIN";
}
@PreAuthorize("hasRole(@Roles.ROLE_ADMIN)")
Authorization annotations can be placed at the Service or Controller level. For this training, place them at the service implementation level. Verify the correctness of these configurations.
-
Attempt to invoke an endpoint with a user who lacks access to it and check the response.
-
Do the same with a user who has the proper roles and verify that the correct action is executed.
Right now all Service and some of UseCase tests will not work. They fails due to the fact that in all that endpoints we will execute service implementation which requires proper authorization. Please fix test: * UseCases test by providing user which should be used while executing test. Please add following annotation to test on method or class level. Please remember to adjust proper role
@WithMockUser(username = "test", roles = "ADMIN")
-
For Service tests, multiple approaches are possible. Extend the tests to include user data and CSRF in the request. Additional examples are available in the solution branch’s different test classes.
mockMvc.perform(delete("/person/{id}", personId)
.contentType(MediaType.APPLICATION_JSON)
.with(user("admin").roles("MAINTAINER"))
.with(csrf()))
.andExpect(status().isOk());
Up to this point, all user data has been stored in memory. Now, attempt to utilize a database to store user information. Adjust relevant components to retrieve data from the database during authentication.
-
Create an appropriate migration script to add users and roles. Default schema can be found under org/springframework/security/core/userdetails/jdbc/users.ddl
-
Replace the current
UserDetailsService
with one that interfaces with the database, such as JdbcUserDetailsManager. -
Ensure that after adding a new user to the database, logging in with the new credentials is possible.
-
Introduce a new endpoint to create a Person.
-
Instead of relying on user-provided data, utilize the currently logged-in user for person details.
-
For simplicity, assume that emails follow the format:
[username]@example.com
.
-
Define more granular authorities to safeguard business logic. For example:
-
CREATE_USER
-
DELETE_USER
-
…
-
-
Adjust the authorization of business logic to incorporate these new authorities.
-
Establish a mapping of ROLE → LIST_OF_AUTHORITIES. Develop a mechanism that, during authentication, resolves user roles and adds all corresponding authorities to the user.