Description
Let's set up the authentication for our service. Of course, you can implement it yourself, but it is considered good practice to use an already tested and reliable implementation. Fortunately, Spring includes the Spring Security module that contains the right methods.
In this stage, you need to provide the HTTP Basic authentication for our REST service with JDBC implementations of UserDetailService for user management. You will need an endpoint for registering users at POST api/auth/signup.
To test the authentication, you need to add another endpoint GET api/empl/payment/ that will be available only for authenticated users. For persistence, put users in the database. Our service will include an H2 database.
To run the tests, the application.properties file should contain the following line: spring.datasource.url=jdbc:h2:file:../service_db.
Since 2.3.0, Spring Boot hides error messages by default, to pass the tests, you need to add the following line: server.error.include-message=always to the application.properties file. For more detail, refer to the Spring Boot Release Notes.
We suggest customizing the solution for the tasks of our service. First, don't forget that we are implementing a REST architecture. It means that we don't have sessions. The HTTP basic mechanism is selected for authentication, and in case of an unauthorized access attempt, the service must respond with the appropriate status. Also, configure the access for the API. To do this, you need to configure the HttpSecurity object with the method chaining like this:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.httpBasic(Customizer.withDefaults())
.exceptionHandling(ex -> ex.authenticationEntryPoint(restAuthenticationEntryPoint)) // Handle auth errors
.csrf(csrf -> csrf.disable()) // For Postman
.headers(headers -> headers.frameOptions().disable()) // For the H2 console
.authorizeHttpRequests(auth -> auth // manage access
.requestMatchers(HttpMethod.POST, "/api/auth/signup").permitAll()
// other matchers
)
.sessionManagement(sessions -> sessions
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // no session
);
return http.build();
}RestAuthenticationEntryPoint is an instance of the class that implements the AuthenticationEntryPoint interface. This endpoint handles authentication errors. For example:
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}If you don't know how to use exceptions in Spring Boot, please, take a look:
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Some error meassage")
public class UserExistException extends RuntimeException { }You will also need some security dependencies in Gradle:
dependencies {
... other dependencies ...
implementation 'org.springframework.boot:spring-boot-starter-security'
}Objectives
Add the Spring security to your project and configure the HTTP basic authentication;
For storing users and passwords, add a JDBC implementation of UserDetailsService with an H2 database;
Change the POST api/auth/signup endpoint. It must be available to unauthorized users for registration and accepts data in the JSON format:
{
"name": "<String value, not empty>",
"lastname": "<String value, not empty>",
"email": "<String value, not empty>",
"password": "<String value, not empty>"
}If OK, provide the HTTP OK status (200) and the following body:
{
"id": "<Long value, not empty>",
"name": "<String value, not empty>",
"lastname": "<String value, not empty>",
"email": "<String value, not empty>"
}As a unique login for authentication, take the value from the email field. The value of the email field must be case insensitive. Id is a unique identifier that the service assigns to the user. If an email is occupied, the service should respond as shown below. The rest of the error messages are the same as in the previous stage:
{
"timestamp": "data",
"status": 400,
"error": "Bad Request",
"message": "User exist!",
"path": "/api/auth/signup"
}Add the GET api/empl/payment/ endpoint that allows for testing the authentication. It should be available only to authenticated users and return a response in the JSON format representing the user who has sent the request:
{
"id": "<Long value, not empty>",
"name": "<String value, not empty>",
"lastname": "<String value, not empty>",
"email": "<String value, not empty>"
}The email field must contain the user's login who has sent the request. Error message for the non-authenticated or wrong user should have the 401 (Unauthorized) status.
Examples
Example 1: a POST request for api/auth/signup with the correct user
Request body:
{
"name": "John",
"lastname": "Doe",
"email": "[email protected]",
"password": "secret"
}Response: 200 OK
Response body:
{
"id": 1,
"name": "John",
"lastname": "Doe",
"email": "[email protected]"
}Example 2: a POST request for api/auth/signup with the occupied email
Request body:
{
"name": "John",
"lastname": "Doe",
"email": "[email protected]",
"password": "secret"
}Response: 400 Bad Request
Response body:
{
"timestamp": "<data>",
"status": 400,
"error": "Bad Request",
"message": "User exist!",
"path": "/api/auth/signup"
}Example 3: a POST request for api/auth/signup with the wrong format of the user JSON
Request body:
{
"lastname": "Doe",
"email": "[email protected]",
"password": "secret"
}Response: 400 Bad Request
Response body:
{
"timestamp": "<date>",
"status": 400,
"error": "Bad Request",
"path": "/api/auth/signup"
}Example 4: a GET request for /api/empl/payment with the correct authentication, username = [email protected], password = secret
Response: 200 OK
Response body:
{
"id": 1,
"name": "John",
"lastname": "Doe",
"email": "[email protected]"
}Example 5: a GET request for /api/empl/payment with the correct authentication; username = [email protected], password = secret
Response: 200 OK
Response body:
{
"id": 1,
"name": "John",
"lastname": "Doe",
"email": "[email protected]"
}
Example 6: a GET request for /api/empl/payment with the wrong authentication; username = [email protected], password = no_secret
Response body:
{
"timestamp": "<data>",
"status": 401,
"error": "Unauthorized",
"message": "",
"path": "/api/empl/payment"
}