summaryrefslogtreecommitdiff
path: root/microservices
diff options
context:
space:
mode:
authorKamal Wickramanayake <kamal@inbox.lk>2026-07-06 06:32:08 +0530
committerKamal Wickramanayake <kamal@inbox.lk>2026-07-06 06:32:08 +0530
commit5ca98df7caebf56990e26aa83a1a2b626ba16660 (patch)
treee71cd8298a49c95d7a03697ef4324b342b399f3e /microservices
parent504045fb23fa75a520a4d2665aea7d423db1fe14 (diff)
Microservices: Documentation improvedHEADmaster
Diffstat (limited to 'microservices')
-rw-r--r--microservices/01-oauth2-server/README8
-rw-r--r--microservices/01-oauth2-server/src/main/resources/application.yaml2
-rw-r--r--microservices/02-api-gateway/README16
-rw-r--r--microservices/02-api-gateway/linux/deb/package/etc/ceit-fs-api-gateway/application.yaml5
-rw-r--r--microservices/02-api-gateway/src/main/java/com/example/apigateway/config/RoleHeaderRelayFilter.java13
-rw-r--r--microservices/02-api-gateway/src/main/resources/application.yaml6
-rw-r--r--microservices/03-resource-server/README23
-rw-r--r--microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java47
-rw-r--r--microservices/04-react-app/README.md5
-rw-r--r--microservices/04-react-app/src/contexts/AuthContext.jsx7
10 files changed, 102 insertions, 30 deletions
diff --git a/microservices/01-oauth2-server/README b/microservices/01-oauth2-server/README
index f91aac5..88bc575 100644
--- a/microservices/01-oauth2-server/README
+++ b/microservices/01-oauth2-server/README
@@ -1,10 +1,10 @@
-This project is a simple OAuth2 server using Spring Security.
+This project implements a simple OAuth2 server that uses Spring Authorization Server customized by Spring Security.
-It has been cofigured to authenticate users as defined in the application.yaml file.
+It has been cofigured to authenticate users as defined in the application.yaml file. This is accomplished by the SecurityConfig.java file (with the help from a few other classes).
-However, this same sample project can be extened to authenticate users based on other authentication mechanisms such as LDAP, database and so on.
+However, this same sample project can be modified to authenticate users based on other authentication mechanisms such as LDAP, Microsoft Active Directory, database and so on.
To run:
./mvnw spring-boot:run
-The deb directory content can be used to make a deb file from this project that can be used to deploy this as a service.
+The linux/deb directory content can be used to make a deb file from this project that can be used to deploy this as a service.
diff --git a/microservices/01-oauth2-server/src/main/resources/application.yaml b/microservices/01-oauth2-server/src/main/resources/application.yaml
index b263a5e..cf2c490 100644
--- a/microservices/01-oauth2-server/src/main/resources/application.yaml
+++ b/microservices/01-oauth2-server/src/main/resources/application.yaml
@@ -9,6 +9,7 @@ server:
spring:
application:
name: oauth2-server
+ # Define the API gateway as an OAuth2 client.
security:
oauth2:
authorizationserver:
@@ -31,6 +32,7 @@ spring:
- "profile"
require-authorization-consent: false
+# Define test users and their passwords. You can add more if you want.
app:
users:
- username: admin
diff --git a/microservices/02-api-gateway/README b/microservices/02-api-gateway/README
new file mode 100644
index 0000000..310aeb0
--- /dev/null
+++ b/microservices/02-api-gateway/README
@@ -0,0 +1,16 @@
+This directory contains an API gateway implemented using Spring Cloud API Gateway.
+
+Look at the application.yaml as to how the API gateway forwards requests to backend services.
+
+Additional notes:
+ After a user successfully authenticates to the external OAuth2 authorization server and is redirected back to this API Gatway, the authorization server may not always return user role details. Some authorization servers may return such details but some others may not based on their configurations. Assuming the roles are defined within the specific application (specific backend services used and the React frontend) instead by the authorization server, the API gateway tries to find the user roles by accessing an internal REST service. This is coded in the SecurityConfig.java and uses the property app.userRolesUrl defined in application.yaml file.
+
+ The JavaScript frontend application can call the /api/security/me endpoint of this API Gateway for it to find the roles of authenticated users (so that the link display visibility and route availability can be controlled within the JavaScript frontend application based on user roles.) Exposing this endpoint has been coded in the UserRestController.java file.
+
+ During user authention this API gateway loads the role data (from the earlier said backend endpoint). After that, this API gateway adds the user roles to HTTP requests that are forworded to the backend services. This is coded in the RoleHeaderRelayFilter.java file.
+
+ If a backend service needs to determine the roles of a user on behalf of whome a request is being served for, the backend service may use the HTTP headers sent by this API gateway. Keep in mind that this header information may not be trused by a backend service if the backend service is not deployed in an access restricted network. Otherwise, an attacker may send requests to the backend service with forged HTTP headers to force the backend service to assume different user roles.
+
+ This API gateway has been configured to forward the OAuth2 access token of a user to the backend service. This is accomplished by the TokenRelay filter added to the routes defined in application.yaml. A backend service configured as an OAuth2 resource server will use this token to detect the user on behalf of whome the API gatway forwarded a request. The backend service can trust the username determined from the access token even if the backend service is not deployed in an access restricted network (as opposed to trusting the details in HTTP headers).
+
+ Look at the README file of the sample resource server for further details about how the backend service determins the user and user roles. \ No newline at end of file
diff --git a/microservices/02-api-gateway/linux/deb/package/etc/ceit-fs-api-gateway/application.yaml b/microservices/02-api-gateway/linux/deb/package/etc/ceit-fs-api-gateway/application.yaml
index d037d5b..d5197fd 100644
--- a/microservices/02-api-gateway/linux/deb/package/etc/ceit-fs-api-gateway/application.yaml
+++ b/microservices/02-api-gateway/linux/deb/package/etc/ceit-fs-api-gateway/application.yaml
@@ -49,6 +49,7 @@ spring:
- PrefixPath=/myservices
- TokenRelay # Add oauth (or jwt) token to request header before forwarding
+ # Register this API gateway as an OAuth2 client to the Authorization server.
security:
oauth2:
client:
@@ -66,6 +67,10 @@ spring:
scope:
- openid
- profile
+
+# Extra OAuth2 client configuration. Doing this configuration here is optional and can instead be
+# done without the com.c4-soft.springaddons dependency. In that case, this configuration can be
+# done in a custom security configuration class. This is just convenient compared to that option.
com:
c4-soft:
springaddons:
diff --git a/microservices/02-api-gateway/src/main/java/com/example/apigateway/config/RoleHeaderRelayFilter.java b/microservices/02-api-gateway/src/main/java/com/example/apigateway/config/RoleHeaderRelayFilter.java
index e3bb241..0bdef6b 100644
--- a/microservices/02-api-gateway/src/main/java/com/example/apigateway/config/RoleHeaderRelayFilter.java
+++ b/microservices/02-api-gateway/src/main/java/com/example/apigateway/config/RoleHeaderRelayFilter.java
@@ -4,8 +4,6 @@ import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -33,11 +31,11 @@ public class RoleHeaderRelayFilter implements GlobalFilter, Ordered {
.flatMap(authentication -> {
if (authentication != null && authentication.getPrincipal() instanceof OAuth2User) {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
-
+
// Extract username (adjust the attribute key based on your IdP provider)
String username = oAuth2User.getAttribute("preferred_username");
if (username == null) {
- username = oAuth2User.getName();
+ username = oAuth2User.getName();
}
// Extract roles/authorities
@@ -48,13 +46,14 @@ public class RoleHeaderRelayFilter implements GlobalFilter, Ordered {
// Mutate the request with new headers
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-User-Roles", roles)
- .header("X-User-Name", username) // Changed 'U-User-Name' to standard 'X-' format for consistency, or use your exact key
+ .header("X-User-Name", username) // Changed 'U-User-Name' to standard 'X-' format for
+ // consistency, or use your exact key
.build();
// Proceed with the mutated exchange
return chain.filter(exchange.mutate().request(mutatedRequest).build());
}
-
+
// If not authenticated via OAuth2User, just pass through
return chain.filter(exchange);
})
@@ -64,6 +63,6 @@ public class RoleHeaderRelayFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// Run after the Security Web Filter loads the context, but before routing
- return Ordered.LOWEST_PRECEDENCE - 1;
+ return Ordered.LOWEST_PRECEDENCE - 1;
}
}
diff --git a/microservices/02-api-gateway/src/main/resources/application.yaml b/microservices/02-api-gateway/src/main/resources/application.yaml
index f51dbfe..4e1f9e7 100644
--- a/microservices/02-api-gateway/src/main/resources/application.yaml
+++ b/microservices/02-api-gateway/src/main/resources/application.yaml
@@ -2,6 +2,8 @@ server:
port: 8050
address: 127.0.0.1
+# From where this API gatway can load roles of authenticated users?
+# Look at the README file for details.
app:
userRolesUrl: "http://localhost:8052/myservices/security/open/users/{username}/roles"
@@ -49,6 +51,7 @@ spring:
- PrefixPath=/myservices
- TokenRelay # Add oauth (or jwt) token to request header before forwarding
+ # Register this API gateway as an OAuth2 client with the OAuth2 authorization server
security:
oauth2:
client:
@@ -66,6 +69,9 @@ spring:
scope:
- openid
- profile
+
+# Instead of coding a SecurityConfig class with OAuth2 configurations, com.c4-soft.springaddons dependency
+# allows configuring some OAuth2 behaviour within this application.yaml file. This is just convenient.
com:
c4-soft:
springaddons:
diff --git a/microservices/03-resource-server/README b/microservices/03-resource-server/README
index 095e8c5..9d0fe77 100644
--- a/microservices/03-resource-server/README
+++ b/microservices/03-resource-server/README
@@ -1,23 +1,30 @@
-This project exposes REST endpoints (accessed by API gateway) and the endpoints are secured by OAuth2.
+This project exposes REST endpoints (accessed by API gateway). They are secured by OAuth2. There is one REST endpoint defined in AuthRestController.java that is not secured. This endpoint is used by the API gateway to determine the roles of authenticating users.
+
+How to determine the user on behalf of whome a request is being made by the API gateway and the roles of that user?
+
+Since this is secured as an OAuth2 resource server, OAuth2 access token send with HTTP requests can just be used to authenticate the user.
+
+Assuming that the OAuth2 access token doesn't hold user roles, a different strategy should be used to determine user roles. One is to find user roles within this service itself by using the OAuth2 username (which can be trusted). This may for example involve looking for user roles details in a database accessed by this service. That approach can be trusted always but involves additional steps. Another approach is to just use the incoming HTTP request headers (added to the requests by the API gateway). To trust the role details so sent, this service should have been deployed in an access restricted network. Otherwise, an attacker may send HTTP requests with forged headers to make this service assume false user roles. Look at the UserRestController.java for details.
+
Some pom.xml dependencies:
To make this Spring Boot project an OAuth2 resource server (Allow access from OAuth2 clients):
<dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security-oauth2-resource-server</artifactId>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security-oauth2-resource-server</artifactId>
</dependency>
To use H2 in-memory database (as configured in main/src/resources/application.yaml file):
<dependency>
- <groupId>com.h2database</groupId>
- <artifactId>h2</artifactId>
- <scope>runtime</scope>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <scope>runtime</scope>
</dependency>
To use H2 database console accessible via a web browser:
<dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-h2console</artifactId>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-h2console</artifactId>
</dependency>
How to access the H2 database console:
diff --git a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java
index 1b0598d..c7844a0 100644
--- a/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java
+++ b/microservices/03-resource-server/src/main/java/com/example/resourceserver/security/UserRestController.java
@@ -3,11 +3,8 @@ package com.example.resourceserver.security;
import java.security.Principal;
import java.util.Collection;
import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
import java.util.stream.Collectors;
-import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -26,17 +23,41 @@ public class UserRestController {
this.userService = userService;
}
+ /**
+ * What is in Principal?
+ *
+ * Note that we are returning the details just for testing (here and in some
+ * other methods below). But what is typically done is not returning such
+ * details but use the details within the method itself to determine who is the
+ * calling user and the roles of that user.
+ *
+ * @param principal
+ * @return
+ */
@GetMapping("/test")
public String test(Principal principal) {
return principal.toString();
}
+ /**
+ * principal.getName() returns the OAuth2 authenticated user name. Can be
+ * trusted within this method.
+ *
+ * @param principal
+ * @return
+ */
@GetMapping("/test-username")
public String testName(Principal principal) {
return principal.getName();
}
- // Get roles using the Authentication object
+ /**
+ * Roles returned by authentication.getAuthorities() may not always have custom
+ * roles.
+ *
+ * @param authentication
+ * @return
+ */
@GetMapping("/test-roles-auth")
public Collection<String> getRoles(Authentication authentication) {
return authentication.getAuthorities().stream()
@@ -44,16 +65,28 @@ public class UserRestController {
.collect(Collectors.toList());
}
- // Inspect raw JWT claims directly
+ /**
+ * Inspect raw JWT claims directly.
+ *
+ * @param jwt
+ * @return
+ */
@GetMapping("/test-roles-jwt")
public Map<String, Object> getClaims(@AuthenticationPrincipal Jwt jwt) {
return jwt.getClaims(); // Extract custom JSON fields containing roles
}
- /* Not very secure because unverifiable headers can be introduced by unwanted parties */
+ /**
+ * Within a method, use the X-User-Roles HTTP header to determine the user
+ * roles.
+ *
+ * Not very secure because unverifiable headers may be introduced by attackers.
+ * So to trust the roles so received, this service should have been
+ * deployed in an access restricted network environment.
+ */
@GetMapping("/test-roles-headers")
public String getRolesFromHeaders(@RequestHeader("X-User-Name") String username,
@RequestHeader("X-User-Roles") String roles) {
- return "Roles: " + roles + ", username: " + username ;
+ return "Roles: " + roles + ", username: " + username;
}
}
diff --git a/microservices/04-react-app/README.md b/microservices/04-react-app/README.md
index 8daba90..37bb944 100644
--- a/microservices/04-react-app/README.md
+++ b/microservices/04-react-app/README.md
@@ -1,2 +1,5 @@
-# OAuth2 secured api gateway access
+# A React.js frontend that uses an OAuth2 secured api gateway to access backend services
+# Login, logout, determining user roles
+
+Look at the contexts/AuthContext.jsx \ No newline at end of file
diff --git a/microservices/04-react-app/src/contexts/AuthContext.jsx b/microservices/04-react-app/src/contexts/AuthContext.jsx
index 9bb5937..c02b7a8 100644
--- a/microservices/04-react-app/src/contexts/AuthContext.jsx
+++ b/microservices/04-react-app/src/contexts/AuthContext.jsx
@@ -9,7 +9,7 @@ export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
useEffect(() => {
- // Fetch user data from your backend API
+ // Fetch user data from API gateway
const fetchUser = async () => {
// axios is directly used from Axios library to avoid interceptor
// added in services/api.js
@@ -32,7 +32,7 @@ export function AuthProvider({ children }) {
const login = () => {
// Start single sign-on process (Using OAuth2, OpenID)
// Access /ui of API gateway.
- // API gateway will redirect user to authorization server if the user is not logged in
+ // API gateway will redirect user to authorization server if the user is not logged in.
// If logged in, API gateway will redirect browser to React app itself reloading the app.
window.location.href = "/bff/ui";
}
@@ -46,6 +46,7 @@ export function AuthProvider({ children }) {
withCredentials: true
});
// Redirect browser to logout from authorization server.
+ // This makes sure user is logged out from both the API gateway and OAuth2 authorization server.
window.location.href = response.headers.location;
// setUser(null) is not needed since browser is redirected.
} catch (error) {
@@ -81,7 +82,7 @@ export function AuthProvider({ children }) {
return false;
}
- // Provide the state and the modifier function as a value object
+ // Add the user data and some functions to the AuthContext
return (
<AuthContext value={{ user, login, logout, isLoggedIn, isAdmin, isInRole, isInRoles }}>
{children}