Securing Spring Boot Microservices with OAuth2

In this tutorial, we will secure a Spring Boot microservices architecture using OAuth2. We will cover setting up an OAuth2 Authorization Server, configuring Spring Boot microservices to use OAuth2 for authentication and authorization, and running the services.

Simple Spring Boot Microservices Architecture

Here's a diagram representing a simple Spring Boot microservices architecture with an API Gateway, Employee Service, Department Service, and an Authorization Server for OAuth2 security. 
Securing Spring Boot Microservices with OAuth2

Explanation:

  1. API Gateway:

    • The API Gateway serves as the entry point for all client requests. It routes requests to the appropriate microservice based on the URL patterns.
    • It communicates with the Authorization Server to validate access tokens for secured endpoints.
  2. Authorization Server:

    • The Authorization Server is responsible for issuing OAuth2 tokens for authentication and authorization.
    • It interacts with a User Database to validate user credentials.
  3. Employee Service:

    • This microservice handles CRUD operations related to employees.
    • It communicates with the Employee Database to store and retrieve employee data.
    • It requires a valid OAuth2 token from the Authorization Server to serve requests.
  4. Department Service:

    • This microservice handles CRUD operations related to departments.
    • It communicates with a database (not explicitly shown in the diagram) to store and retrieve department data.
    • It also requires a valid OAuth2 token from the Authorization Server to serve requests.

Data Flow:

  1. A client sends a request to the API Gateway.
  2. The API Gateway checks if the request is authenticated. If not, it redirects the client to the Authorization Server to obtain an access token.
  3. The Authorization Server validates the user credentials and issues an access token.
  4. The client includes the access token in subsequent requests to the API Gateway.
  5. The API Gateway routes the requests to the appropriate microservice (Employee Service or Department Service).
  6. The microservices validate the access token with the Authorization Server.
  7. Upon successful validation, the microservices process the request and interact with their respective databases to fetch or store data.
  8. The response is sent back to the API Gateway, which then forwards it to the client.

Prerequisites

Before we start, ensure you have the following:

  • Java Development Kit (JDK) installed
  • Apache Maven installed
  • Docker installed
  • Docker Compose installed
  • An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed

Overview of the Microservices

We will create three Spring Boot microservices:

  1. Authorization Server: Issues OAuth2 tokens.
  2. Employee Service: Manages employee information.
  3. API Gateway: Routes requests and handles security with OAuth2.

Step 1: Create the Authorization Server

1.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: auth-server
    • Name: auth-server
    • Description: OAuth2 Authorization Server
    • Package Name: com.example.authserver
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Web
      • Spring Security
      • OAuth2 Authorization Server
      • Spring Data JPA
      • H2 Database
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

1.2 Update application.properties

Open the application.properties file located in the src/main/resources directory and add the following configuration:

server.port=9000

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

1.3 Create User Entity

Create a User entity class in the com.example.authserver.model package:

package com.example.authserver.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    // Getters and Setters
}

1.4 Create User Repository

Create a UserRepository interface in the com.example.authserver.repository package:

package com.example.authserver.repository;

import com.example.authserver.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

1.5 Create User Service

Create a UserService class in the com.example.authserver.service package:

package com.example.authserver.service;

import com.example.authserver.model.User;
import com.example.authserver.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    @Autowired
    public UserService(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    public User saveUser(User user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        return userRepository.save(user);
    }

    public User findByUsername(String username) {
        return userRepository.findByUsername(username);
    }
}

1.6 Create Security Configuration

Create a SecurityConfig class in the com.example.authserver.config package:

package com.example.authserver.config;

import com.example.authserver.service.UserService;
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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableWebSecurity
@EnableAuthorizationServer
@EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {

    private final UserService userService;

    @Autowired
    public SecurityConfig(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/oauth/**").permitAll()
            .anyRequest().authenticated();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

1.7 Create Authorization Server Configuration

Create an AuthorizationServerConfig class in the com.example.authserver.config package:

package com.example.authserver.config;

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.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;

    @Autowired
    public AuthorizationServerConfig(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("client_id")
            .secret("{noop}client_secret")
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("read", "write")
            .accessTokenValiditySeconds(3600)
            .refreshTokenValiditySeconds(7200);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
            .tokenStore(tokenStore());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}

Step 2: Create the Employee Service

2.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: employee-service
    • Name: employee-service
    • Description: Employee Service
    • Package Name: com.example.employeeservice
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Web
      • Spring Data JPA
      • Spring Security
      • OAuth2 Resource Server
      • H2 Database
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

2.2 Update application.properties

Open the application.properties file located in the src/main/resources directory and add the following configuration:

server.port=8081



spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update

spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:9000/oauth/token_key

2.3 Create Employee Entity

Create an Employee entity class in the com.example.employeeservice.model package:

package com.example.employeeservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;

    // Getters and Setters
}

2.4 Create Employee Repository

Create an EmployeeRepository interface in the com.example.employeeservice.repository package:

package com.example.employeeservice.repository;

import com.example.employeeservice.model.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

2.5 Create Employee Controller

Create an EmployeeController class in the com.example.employeeservice.controller package:

package com.example.employeeservice.controller;

import com.example.employeeservice.model.Employee;
import com.example.employeeservice.repository.EmployeeRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    private final EmployeeRepository employeeRepository;

    @Autowired
    public EmployeeController(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @GetMapping
    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @PostMapping
    public Employee createEmployee(@RequestBody Employee employee) {
        return employeeRepository.save(employee);
    }
}

2.6 Create Security Configuration

Create a SecurityConfig class in the com.example.employeeservice.config package:

package com.example.employeeservice.config;

import org.springframework.context.annotation.Bean;
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.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorizeRequests ->
                authorizeRequests
                    .requestMatchers("/employees/**").authenticated()
                    .anyRequest().permitAll()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt());
        return http.build();
    }
}

Step 3: Create the API Gateway

3.1 Create a Spring Boot Project

  1. Open Spring Initializr:

  2. Configure Project Metadata:

    • Project: Maven Project
    • Language: Java
    • Spring Boot: Select the latest version of Spring Boot 3.2
    • Group: com.example
    • Artifact: api-gateway
    • Name: api-gateway
    • Description: API Gateway
    • Package Name: com.example.apigateway
    • Packaging: Jar
    • Java Version: 17 (or your preferred version)
    • Click Next.
  3. Select Dependencies:

    • On the Dependencies screen, select the dependencies you need:
      • Spring Cloud Gateway
      • Spring Security
      • OAuth2 Resource Server
    • Click Next.
  4. Generate the Project:

    • Click Generate to download the project zip file.
    • Extract the zip file to your desired location.
  5. Open the Project in Your IDE:

    • Open your IDE and import the project as a Maven project.

3.2 Update application.yml

Create an application.yml file in the src/main/resources directory and configure it as follows:

server:
  port: 8080

spring:
  application:
    name: api-gateway

  cloud:
    gateway:
      routes:
        - id: employee_service
          uri: http://localhost:8081
          predicates:
            - Path=/employees/**
      default-filters:
        - TokenRelay

spring.security.oauth2.resourceserver.jwt.jwk-set-uri: http://localhost:9000/oauth/token_key

3.3 Create Security Configuration

Create a SecurityConfig class in the com.example.apigateway.config package:

package com.example.apigateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeExchange(exchanges ->
                exchanges
                    .pathMatchers("/employees/**").authenticated()
                    .anyExchange().permitAll()
            )
            .oauth2ResourceServer(oauth2 -> oauth2.jwt());
        return http.build();
    }
}

Step 4: Running the Microservices

4.1 Running Authorization Server

  1. Open the AuthServerApplication class in the src/main/java/com/example/authserver directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

4.2 Running Employee Service

  1. Open the EmployeeServiceApplication class in the src/main/java/com/example/employeeservice directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

4.3 Running API Gateway

  1. Open the ApiGatewayApplication class in the src/main/java/com/example/apigateway directory.
  2. Click the green Run button in your IDE or use the terminal to run the application:
    ./mvnw spring-boot:run
    

Step 5: Testing the Application

You can use tools like Postman to test the authentication and secure access to the microservices.

5.1 Get Access Token

  1. Create a new POST request to http://localhost:9000/oauth/token.
  2. In the Body tab, select x-www-form-urlencoded and add the following parameters:
    • grant_type: password
    • client_id: client_id
    • client_secret: client_secret
    • username: testuser
    • password: testpassword
  3. Send the request and copy the access token from the response.

5.2 Access Secured Employee Service

  1. Create a new GET request to http://localhost:8080/employees.
  2. Add the Authorization header with the value Bearer <ACCESS_TOKEN>.
  3. Send the request and verify that you receive the list of employees.

Conclusion

In this tutorial, we created a microservices architecture with Spring Boot and secured it using OAuth2. We built an authorization server to issue tokens, secured the employee service with OAuth2, and created an API Gateway to route and secure requests. This setup provides a solid foundation for developing secure microservices architectures.


Comments