This chapter explains how to secure and test your Spring Boot backend. Securing your backend is a crucial part of code development. In the testing part of this chapter, we will create some unit tests in relation to our backend—these will make your backend code easier to maintain. We will use the database application that we created in the previous chapter as a starting point.
In this chapter, we will cover the following topics:
The Spring Boot application that we created in the previous chapters is required.
The following GitHub link will also be required: https://github.com/PacktPublishing/Full-Stack-Development-with-Spring-Boot-and-React/tree/main/Chapter05.
Check out the following video to see the Code in Action: https://bit.ly/3Gv9wVD
Spring Security (https://spring.io/projects/spring-security) provides security services for Java-based web applications. The Spring Security project was started in 2003 and was previously named Acegi Security System for Spring.
By default, Spring Security enables the following features:
You can include Spring Security in your application by adding the following dependencies to the pom.xml file. The first dependency is for the application and the second is for testing:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
When you start your application, you can see from the console that Spring Security has created an in-memory user with a username of user. The user's password can be seen in the console output, as illustrated here:
If there is no password in the console, try to restart your project by pressing the red Terminate button in the console and rerun your project.
Now, if you make a GET request to your application programming interface (API) root endpoint, you will see that it is now secured. Open your web browser and navigate to http://localhost:8080/api. Now, you will see that you are redirected to the Spring Security default login page, as illustrated in the following screenshot:
To be able to make a successful GET request, we have to authenticate. Type user into the Username field and copy the generated password from the console to the Password field. With authentication, we can see that the response contains our API resources, as illustrated in the following screenshot:
To configure how Spring Security behaves, we have to add a new configuration class that extends WebSecurityConfigurerAdapter. Create a new class called SecurityConfig in your application root package (com.packt.cardatabase). The following source code shows the structure of the security configuration class. The @Configuration and @EnableWebSecurity annotations switch off the default web security configuration, and we can define our own configuration in this class. Inside the configure(HttpSecurity http) method, we can define which endpoints in our application are secure and which are not. We don't actually need this method yet because we can use the default settings where all the endpoints are secured:
package com.packt.cardatabase;
import org.springframework.context.annotation.
Configuration;
import org.springframework.security.config.annotation.
web.builders.HttpSecurity;
import org.springframework.security.config.
annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.
web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws
Exception {
}
}
We can also add in-memory users to our application by adding the userDetailsService() method to our SecurityConfig class. The following source code of the method will create an in-memory user with a username of user and a password of password:
// SecurityConfig.java
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
The use of in-memory users is fine in the development phase, but the real application should save users in the database.
Important Note
The withDefaultPasswordEncoder() method should only be used for demonstration purposes and it is not safe for production.
To save users to the database, you have to create a user entity class and repository. Passwords shouldn't be saved to the database in plaintext format. Spring Security provides multiple hashing algorithms, such as bcrypt, that you can use to hash passwords. The following steps show you how to implement this:
package com.packt.cardatabase.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(nullable=false, updatable=false)
private Long id;
@Column(nullable=false, unique=true)
private String username;
@Column(nullable=false)
private String password;
@Column(nullable=false)
private String role;
public User() {}
public User(String username, String password,
String role) {
super();
this.username = username;
this.password = password;
this.role = role;
}
Here is the rest of the User.java source code with the getters and setters:
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
package com.packt.cardatabase.domain;
import java.util.Optional;
import org.springframework.data.repository.
CrudRepository;
public interface UserRepository extends CrudRepository
<User, Long> {
Optional<User> findByUsername(String username);
}
package com.packt.cardatabase.service;
import java.util.Optional;
import org.springframework.beans.factory.annotation.
Autowired;
import org.springframework.security.core.userdetails.
User.UserBuilder;
import org.springframework.security.core.userdetails.
UserDetails;
import org.springframework.security.core.userdetails.
UserDetailsService;
import org.springframework.security.core.userdetails.
UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.packt.cardatabase.domain.User;
import com.packt.cardatabase.domain.UserRepository;
@Service
public class UserDetailsServiceImpl implements
UserDetailsService {
@Autowired
private UserRepository repository;
@Override
public UserDetails loadUserByUsername(String
username)
throws UsernameNotFoundException {
Optional<User> user =
repository.findByUsername(username);
UserBuilder builder = null;
if (user.isPresent()) {
User currentUser = user.get();
builder =
org.springframework.security.core.userdetails.
User.withUsername(username);
builder.password(currentUser.getPassword());
builder.roles(currentUser.getRole());
} else {
throw new UsernameNotFoundException("User not
found.");
}
return builder.build();
}
}
package com.packt.cardatabase;
import org.springframework.beans.factory.annotation.
Autowired;
import org.springframework.context.annotation.
Configuration;
import org.springframework.security.config.annotation.
authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.
web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation
.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.
BCryptPasswordEncoder;
import com.packt.cardatabase.service.
UserDetailsServiceImpl;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends
WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
public void configureGlobal
(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
@Autowired
private UserRepository urepository;
@Override
public void run(String... args) throws Exception {
// Add owner objects and save to db
Owner owner1 = new Owner("John", "Johnson");
Owner owner2 = new Owner("Mary", "Robinson");
orepository.saveAll(Arrays.asList(owner1,
owner2));
// Add car object and link to owners and save to
db
Car car1 = new Car("Ford", "Mustang", "Red",
"ADF-1121", 2021, 59000, owner1);
Car car2 = new Car("Nissan", "Leaf", "White",
"SSJ-3002", 2019, 29000, owner2);
Car car3 = new Car("Toyota", "Prius", "Silver",
"KKO-0212", 2020, 39000, owner2);
repository.saveAll(Arrays.asList(car1, car2,
car3));
for (Car car : repository.findAll()) {
logger.info(car.getBrand() + " " +
car.getModel());
}
// Username: user, password: user
urepository.save(new User("user",
"$2a$10$NVM0n8ElaRgg7zWO1CxUdei7vWoPg91Lz2aYavh9.f9q0e4bRadue","USER"));
// Username: admin, password: admin
urepository.save(new User("admin", "$2a$10$8cjz47bjbR4Mn8GMg9IZx.vyjhLXR/SKKMSZ9.mP9vpMu0ssKi8GW", "ADMIN"));
}
Important Note
BCrypt is a strong hashing function that was designed by Niels Provos and David Mazières. Here is an example of a BCrypt hash that is generated from the admin string:
$2a$10$8cjz47bjbR4Mn8GMg9IZx.vyjhLXR/SKKMSZ9.mP9vpMu0ssKi8GW
$2a represents the algorithm version and $10 represents the strength of the algorithm. The default strength of Spring Security's BcryptPasswordEncoder class is 10. BCrypt generates a random salt in hashing, therefore the hashed result is always different.
After running your application, you will see that there is now a user table in the database and that two user records are saved with hashed passwords, as illustrated in the following screenshot:
You can see a GET request to the /api endpoint using the admin user in the following screenshot. We can also use Postman and basic authentication:
package com.packt.cardatabase.domain;
import org.springframework.data.repository.
CrudRepository;
import org.springframework.data.rest.core.annotation.
RepositoryRestResource;
@RepositoryRestResource(exported = false)
public interface UserRepository extends CrudRepository
<User, Long> {
}
Now, if you make a GET request to the /users endpoint, you will see that /users endpoint is not visible anymore, as shown in the following screenshot:
Next, we will start to implement authentication using a JWT.
In the previous section, we covered how to use basic authentication with a RESTful web service. This method cannot be used when we develop our own frontend with React, so we are going to use JWT authentication instead. A JWT is a compact way to implement authentication in modern web applications. A JWT is really small in size and can therefore be sent in the Uniform Resource Locator (URL), in the POST parameter, or inside the header. It also contains all the necessary information pertaining to the user.
A JWT contains three different parts, separated by dots: xxxxx.yyyyy.zzzzz. These parts are broken up as follows:
The following diagram shows a simplified representation of the JWT authentication process:
After successful authentication, the requests sent by the client should always contain the JWT that was received in the authentication.
We will use the Java jjwt library (https://github.com/jwtk/jjwt), which is the JWT library for Java and Android. Therefore, we have to add the following dependency to the pom.xml file. The jjwt library is used for creating and parsing JWTs:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
The following steps demonstrate how to enable JWT authentication in our backend. We will start with the login functionality:
Finally, we use the getSubject method to get the username. The whole JwtService source code can be seen here:
package com.packt.cardatabase.service;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Component
public class JwtService {
static final long EXPIRATIONTIME = 86400000; // 1
day in ms
static final String PREFIX = "Bearer";
// Generate secret key. Only for the demonstration
// You should read it from the application
configuration
static final Key key = Keys.secretKeyFor
(SignatureAlgorithm.HS256);
// Generate signed JWT token
public String getToken(String username) {
String token = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()
+ EXPIRATIONTIME))
.signWith(key)
.compact();
return token;
}
// Get a token from request Authorization header,
// verify a token and get username
public String getAuthUser(HttpServletRequest
request) {
String token = request.getHeader
(HttpHeaders.AUTHORIZATION);
if (token != null) {
String user = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token.replace(PREFIX, ""))
.getBody()
.getSubject();
if (user != null)
return user;
}
return null;
}
}
package com.packt.cardatabase.domain;
public class AccountCredentials {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.packt.cardatabase.web;
import org.springframework.beans.factory.annotation
.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.
AuthenticationManager;
import org.springframework.security.authentication.
UsernamePasswordAuthenticationToken;
import org.springframework.security.core.
Authentication;
import org.springframework.web.bind.annotation.
RequestBody;
import org.springframework.web.bind.annotation.
RequestMapping;
import org.springframework.web.bind.annotation.
RequestMethod;
import org.springframework.web.bind.annotation.
RestController;
import com.packt.cardatabase.domain.
AccountCredentials;
import com.packt.cardatabase.service.JwtService;
@RestController
public class LoginController {
@Autowired
private JwtService jwtService;
@Autowired
AuthenticationManager authenticationManager;
@RequestMapping(value="/login", method=RequestMethod
.POST)
public ResponseEntity<?> getToken(@RequestBody
AccountCredentials credentials) {
// Generate token and send it in the response
Authorization
// header
}
}
// LoginController.java
@RequestMapping(value="/login", method=RequestMethod
.POST)
public ResponseEntity<?> getToken(@RequestBody
AccountCredentials credentials) {
UsernamePasswordAuthenticationToken creds =
new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword());
Authentication auth =
authenticationManager.authenticate(creds);
// Generate token
String jwts = jwtService.getToken(auth.getName());
// Build response with the generated token
return ResponseEntity.ok()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + jwts)
.header(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
"Authorization")
.build();
}
package com.packt.cardatabase;
import org.springframework.beans.factory.annotation
.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.
Configuration;
import org.springframework.security.authentication.
AuthenticationManager;
import org.springframework.security.config.annotation.
authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.
web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.
web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.
BCryptPasswordEncoder;
import com.packt.cardatabase.service.
UserDetailsServiceImpl;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
public void configureGlobal
(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BcryptPassword
Encoder());
}
@Bean
public AuthenticationManager
getAuthenticationManager() throws
Exception {
return authenticationManager();
}
}
// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws
Exception {
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.
STATELESS).and()
.authorizeRequests()
// POST request to /login endpoint is not secured
.antMatchers(HttpMethod.POST, "/login").
permitAll()
// All other requests are secured
.anyRequest().authenticated();
}
You can also test login using the wrong password, and see that the response doesn't contain the Authorization header.
We have now finalized the login step, and we will move on to handing authentication in the rest of the incoming requests. In the authentication process, we are using filters that allow us to perform some operations before a request goes to the controller or before a response is sent to a client. The following steps demonstrate the rest of the authentication process:
// Imports
@Component
public class AuthenticationFilter extends
OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Override
protected void doFilterInternal(HttpServletRequest
request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException,
java.io.IOException {
// Get token from the Authorization header
String jws = request.getHeader
(HttpHeaders.AUTHORIZATION);
if (jws != null) {
// Verify token and get user
String user = jwtService.getAuthUser(request);
// Authenticate
Authentication authentication =
new UsernamePasswordAuthenticationToken(user,
null,
java.util.Collections.emptyList());
SecurityContextHolder.getContext()
.setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
}
@Autowired
private AuthenticationFilter authenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated().and()
.addFilterBefore(authenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
Now, we are ready to test the whole workflow. After we run the application, we can first log in by calling the /login endpoint with the POST method and, in the case of a successful login, we will receive a JWT in the Authorization header. Remember to add a valid user inside the body and set the Content-Type header to application/json. The following screenshot illustrates the process:
package com.packt.cardatabase;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.security.core.
AuthenticationException;
import org.springframework.security.web.
AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class AuthEntryPoint implements
AuthenticationEntryPoint {
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws
IOException, ServletException {
response.setStatus
(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType
(MediaType.APPLICATION_JSON_VALUE);
PrintWriter writer = response.getWriter();
writer.println("Error: " + authException.
getMessage());
}
}
// SecurityConfig.java
@Autowired
private AuthEntryPoint exceptionHandler;
Then, modify the configure method, as follows:
// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws
Exception {
http.csrf().disable()
.sessionManagement()
.sessionCreationPolicy
(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling()
.authenticationEntryPoint(exceptionHandler).and()
.addFilterBefore(authenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
We will also add a cross-origin resource sharing (CORS) filter to our security configuration class. This is needed for the frontend, which is sending requests from the other origin. The CORS filter intercepts requests, and if these are identified as cross-origin, it adds proper headers to the request. For that, we will use Spring Security's CorsConfigurationSource interface. In this example, we will allow all origins' HTTP methods and headers. You can define a list of permissible origins, methods, and headers here if you require a more finely graded definition.
// SecurityConfig.java
// Add the following imports
import java.util.Arrays;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.
CorsConfigurationSource;
import org.springframework.web.cors.
UrlBasedCorsConfigurationSource;
// Add Global CORS filter inside the class
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("*"));
config.setAllowedMethods(Arrays.asList("*"));
config.setAllowedHeaders(Arrays.asList("*"));
config.setAllowCredentials(false);
config.applyPermitDefaultValues();
source.registerCorsConfiguration("/**", config);
return source;
}
// localhost:3000 is allowed
config.setAllowedOrigins(Arrays.asList
("http://localhost:3000"));
We also have to add the cors() function to the configure method, as shown in the following code snippet:
// SecurityConfig.java
@Override
protected void configure(HttpSecurity http) throws
Exception {
http.csrf().disable().cors().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.
STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling()
.authenticationEntryPoint(exceptionHandler).and()
.addFilterBefore(authenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
Now, all the functionalities that are required have been implemented in our backend. Next, we will continue with backend unit testing.
The Spring Boot test starter package is added to the pom.xml file by Spring Initializr when we create our project. This is added automatically without any selection in the Spring Initializr page. The code can be seen in the following snippet:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
The Spring Boot test starter provides lots of handy libraries for testing, such as JUnit, Mockito, and AssertJ. In this book, we are using the JUnit 5 version (JUnit Jupiter). If you take a look, your project structure already has its own package created for test classes, as we can see in the following screenshot:
By default, Spring Boot uses an in-memory database for testing. We are now using MariaDB, but H2 can also be used for testing if we add the following dependency to the pom.xml file. The scope defines that the H2 database will only be used for running tests; otherwise, the application will use the MariaDB database:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
If you also want to use the default database for testing, you can use the @AutoConfigureTestDatabase annotation.
For unit testing, we are using JUnit, which is a popular Java-based unit testing library. The following source code shows an example skeleton of the Spring Boot test class. The @SpringBootTest annotation specifies that the class is a regular test class that runs Spring Boot-based tests. The @Test annotation before the method specifies to JUnit that the method can be run as a test case:
@SpringBootTest
public class MyTestsClass {
@Test
public void testMethod() {
// Test case code
}
}
First, we will create our first test case that will test the major functionality of our application before we create any formal test cases. Proceed as follows:
package com.packt.cardatabase;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.
Autowired;
import org.springframework.boot.test.context.
SpringBootTest;
import com.packt.cardatabase.web.CarController;
@SpringBootTest
class CardatabaseApplicationTests {
@Autowired
private CarController controller;
@Test
void contextLoads() {
assertThat(controller).isNotNull();
}
}
You can use @DisplayName annotation to give a more descriptive name to your test case. The name defined in the @DisplayName annotation is shown in the JUnit test runner. The code is illustrated in the following snippet:
@Test
@DisplayName("First example test case")
void contextLoads() {
assertThat(controller).isNotNull();
}
package com.packt.cardatabase;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.
Autowired;
import org.springframework.boot.test.autoconfigure.
orm.jpa.DataJpaTest;
import com.packt.cardatabase.domain.Owner;
import com.packt.cardatabase.domain.OwnerRepository;
@DataJpaTest
public class OwnerRepositoryTest {
@Autowired
private OwnerRepository repository;
}
Optional<Owner> findByFirstname(String firstName);
@Test
void saveOwner() {
repository.save(new Owner("Lucy", "Smith"));
assertThat(repository.findByFirstname
("Lucy").isPresent())
.isTrue();
}
@Test
void deleteOwners() {
repository.save(new Owner("Lisa", "Morrison"));
repository.deleteAll();
assertThat(repository.count()).isEqualTo(0);
}
package com.packt.cardatabase;
import static org.springframework.test.web.servlet.
request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.
servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.
servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.
Autowired;
import org.springframework.boot.test.autoconfigure.
web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.
SpringBootTest;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
public class CarRestTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testAuthentication() throws Exception {
// Testing authentication with correct credentials
this.mockMvc.
perform(post("/login").
content("{"username":"admin","password":
"admin"}").
header(HttpHeaders.CONTENT_TYPE,
"application/json")).
andDo(print()).andExpect(status().isOk());
}
}
Now, when we run the authentication tests, we will see that the test passed, as the following screenshot confirms:
At this point, we have covered the basics of testing in the Spring Boot application, and you have gained the knowledge that's required to implement more test cases for your application.
In this chapter, we focused on securing and testing the Spring Boot backend. First, securing was done with Spring Security. The frontend will be developed with React in upcoming chapters; therefore, we implemented JWT authentication, which is a lightweight authentication method suitable for our needs.
We also covered the basics of testing a Spring Boot application. We used JUnit for unit testing and implemented test cases for JPA and RESTful web service authentication.
In the next chapter, we will set up the environment and tools related to frontend development.
Packt has other great resources available for you to learn about Spring Security and testing. These are listed here:
3.145.191.134