Securing REST APIs with Spring Security
In Spring Boot applications, securing the REST APIs is a critical aspect of developing secure and robust applications. REST APIs are commonly used to expose functionalities to external systems, mobile applications, and web applications. Without proper security measures, these APIs can become targets for attacks, leading to unauthorized access and data breaches. Spring Security is a powerful and customizable authentication and access control framework for Java applications that can be used to secure REST APIs.
The main concept of securing REST APIs with Spring Security involves the following steps and components:
- Authentication: It is the process of verifying the identity of the user or system. Common methods include the username/password, OAuth2, and JWT.
- Authorization: It is the process of granting or denying access to resources based on the authenticated identity of the permissions or roles of the application.
- Token-Based Authentication: Using the tokens like JWT to handle the authentication. This is especially useful for the stateless APIs.
- Role-Based Access Control (RBAC): It defines the access control based on the user roles and permissions of the application.
- Security Filters: Using the filters to the intercept requests and apply the security checks of the application.
Implementation to Secure REST APIs with Spring Security
Below are the implementation steps to secure REST APIs with Spring Security.
Step 1: Create Spring Starter Project
Create a new Spring Boot project using Spring Initializr and add the below dependencies,
- Spring Web
- Spring Security
- Lombok
- Spring DevTools
- MySQL Drivers
- Spring Data for JPA
External Dependencies:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
After the project creation done, the project folder structure will be created like below:
Step 2: Configure Application properties
Open the application.properties file and add the configuration for the MySQL database configuration.
spring.application.name=securing-RESTful-API
spring.datasource.url=jdbc:mysql://localhost:3306/blog_db
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
Step 3: Create the User Entity class
Create a new package named model, in that package, create a new Java class named User.
Go to src > main > java > org.example.securityrestfulapi > model > User and put the below code.
package org.example.securingrestfulapi.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
}
Step 4: Create AuthenticationRequest class
In model package, create a new Java class named AuthenticationRequest.
Go to src > main > java > org.example.securityrestfulapi > model > AuthenticationRequest and put the below code.
package org.example.securingrestfulapi.model;
public class AuthenticationRequest {
private String username;
private String password;
public AuthenticationRequest() {
}
public AuthenticationRequest(String username, String password) {
this.username = username;
this.password = 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;
}
}
Step 5: Create UserRepository class
Create a new package named repository. In that package, create the new Java interface named UserRepository.
Go to src > main > java > org.example.securityrestfulapi > repository > UserRepository and put the below code.
package org.example.securingrestfulapi.repository;
import org.example.securingrestfulapi.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
Step 6: Create CustomUserDetailsService class
Create a new package named service. In that package, create a new Java class and named CustomUserDetailsService .
Go to src > main > java > org.example.securityrestfulapi > service> CustomUserDetailsService and put the below code.
package org.example.securingrestfulapi.service;
import org.example.securingrestfulapi.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.example.securingrestfulapi.model.User;
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 java.util.ArrayList;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
}
}
Step 7: Create JwtUtil class
Create a new package named util. In that package, create a new Java class named JwtUtil .
Go to src > main > java > org.example.securityrestfulapi > util > JwtUtil and put the below code.
package org.example.securingrestfulapi.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Service
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
Step 8: Create JwtRequestFilter class
Create a new package named config. In that package, create a new Java class named JwtRequestFilter .
Go to src > main > java > org.example.securityrestfulapi > config > JwtRequestFilter and put the below code.
package org.example.securingrestfulapi.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.example.securingrestfulapi.service.CustomUserDetailsService;
import org.example.securingrestfulapi.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@AllArgsConstructor
@RequiredArgsConstructor
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.customUserDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
Step 9: Create SecurityConfig class
In config package, create a new Java class named SecurityConfig .
Go to src > main > java > org.example.securityrestfulapi > config > SecurityConfig and put the below code.
package org.example.securingrestfulapi.config;
import org.example.securingrestfulapi.service.CustomUserDetailsService;
import org.example.securingrestfulapi.util.JwtUtil;
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.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().requestMatchers("/api/register", "/api/login").permitAll()
.anyRequest().authenticated()
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtRequestFilter jwtRequestFilter() {
return new JwtRequestFilter(jwtUtil, customUserDetailsService);
}
}
Step 10: Create AuthController class
Create a new package named controller. In that package, create a new Java class named AuthController.
Go to src > main > java > org.example.securityrestfulapi > controller > AuthController and put the below code.
package org.example.securingrestfulapi.controller;
import org.example.securingrestfulapi.model.AuthenticationRequest;
import org.example.securingrestfulapi.model.User;
import org.example.securingrestfulapi.repository.UserRepository;
import org.example.securingrestfulapi.service.CustomUserDetailsService;
import org.example.securingrestfulapi.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/register")
public String registerUser(@RequestBody User user) {
// Encode the user's password
user.setPassword(passwordEncoder.encode(user.getPassword()));
// Save the user to the database
userRepository.save(user);
return "User registered successfully";
}
@PostMapping("/login")
public String loginUser(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
return jwt;
}
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}
Step 11: Main class
No changes are required in the main class.
package org.example.securingrestfulapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecuringResTfulApiApplication {
public static void main(String[] args) {
SpringApplication.run(SecuringResTfulApiApplication.class, args);
}
}
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>securing-RESTful-API</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>securing-RESTful-API</name>
<description>securing-RESTful-API</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Step 12: Run the application
After completing the project, run it as spring boot application. Then the application will start at port 8080.
Step 13: Testing the Application
Registration Endpoint:
http://localhost:8080/api/register
Output:
Login Endpoint:
http://localhost:8080/api/login
Output:
Secure Endpoint:
http://localhost:8080/api/hello
Output:
This example project demonstrates the practical implementation of the user registration, login and securing endpoints using the Spring Security and JWT.