Reverse Proxy
Repository
https://github.com/Mehdi-HAFID/reverse-proxy
Credit
All credit for the BFF and reverse proxy implemented in Nidam goes to Jérôme Wacongne and his article in baeldung.com OAuth2 Backend for Frontend With Spring Cloud Gateway. The BFF and reverse proxy in Nidam is pretty much a copy paste from his article. I strongly recommend to go and read his article.
Why do we need a Reverse Proxy?
The SPA and the BFF -discussed on the next pages- must be served to the user through the same domain. Quoted from the article:
We need the same origin for an SPA and its BFF because:
- the requests are authorized with session cookies between the frontend and the BFF
- Spring session cookies are flagged with
SameSite=Lax
Meaning the SPA and the BFF both must share the same domain. Otherwise, the Session Cookie would not work.
3 applications will be served with the reverse proxy: the SPA React application, the Authorization Server, and the BFF.
Reverse Proxy Configuration
The reverse proxy job is very simple if the context path is:
/react-ui
then redirect to the React application./auth
then redirect to the authorization server./bff
then redirect to the BFF.
In development, the reverse proxy is deployed through http://localhost:7080. All URLs requests go through here.
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.2.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>nidam</groupId>
<artifactId>reverse-proxy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>reverse-proxy</name>
<description>reverse-proxy</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Routing
All of the logic we want the reverse proxy to achieve is simply stated in the application.properties
Applications accessed through: React http://localhost:7080/react-ui Authorization Server http://localhost:7080/auth BFF http://localhost:7080/bff
scheme: http
hostname: localhost
reverse-proxy-port: 7080
react-port: 4001
react-prefix: /react-ui
react-uri: ${scheme}://${hostname}:${react-port}${react-prefix}
authorization-server-port: 4002
authorization-server-prefix: /auth
authorization-server-uri: ${scheme}://${hostname}:${authorization-server-port}${authorization-server-prefix}
bff-port: 7081
bff-prefix: /bff
bff-uri: ${scheme}://${hostname}:${bff-port}
server:
port: ${reverse-proxy-port}
spring:
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
routes:
# SPAs assets
- id: react-ui
uri: ${react-uri}
predicates:
- Path=${react-prefix}/**
# Authorization-server
- id: authorization-server
uri: ${authorization-server-uri}
predicates:
- Path=${authorization-server-prefix}/**
# BFF
- id: bff
uri: ${bff-uri}
predicates:
- Path=${bff-prefix}/**
filters:
- StripPrefix=1
Note that calls to the BFF are stripped of the /bff
path after the redirection. For example, when calling http://localhost:7080/bff/something
the reverse proxy calls http://localhost:7081/something
with /bff
removed. That's not the case with the React application or the authorization server.
They both keep the same path with no changes.
http://localhost:7080/auth/something
=> http://localhost:4002/auth/something
http://localhost:7080/react-ui/something
=> http://localhost:4001/react-ui/something
This is done through spring.cloud.gateway.routes[2].filters: StripPrefix=1
Explaining an authorization server property
On the authorization server page, I did not explain these 2 properties:
server.servlet.contextPath=/auth # 1
server.forward-headers-strategy=framework # 2
- contextPath is simply serving the authorization server with an
/auth
path. so the URL is http://localhost:4002/auth - This will use the reverse proxy URL in addition to the path context, because there is a problem with spring auth server link. You can read my comment about the issue here