Skip to main content

OAuth 2 Spring Resource Server

Now, we get to the backend that will hold the code of your application. A Resource server receives calls from the frontend indirectly (because the bff sets between the two, explained later) through REST API calls with a token generated by the authorization server. After validating the token the resource server accepts and honors the request.

Repository

https://github.com/Mehdi-HAFID/nidam

Requirements

Nidam Spring Resource Sever uses JDK 17, Spring Boot 3.2.0. It is a Spring Resource Server.

pom.xml

Here is the pom.xml declaration.

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>nidam</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nidam</name>
<description>nidam</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<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>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-starter-oidc</artifactId>
<version>7.5.3</version>
</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>
</plugin>
</plugins>
</build>

</project>

Endpoints

Secured Endpoint

The whole point of implementing authentication in your application is securing your REST endpoints. This is a declaration of an endpoint that the frontend will be able to call once authenticated.

src/main/java/nidam/nidam/controller/DemoController.java
@RestController
public class DemoController {
private Logger log = Logger.getLogger(DemoController.class.getName());

@GetMapping("/demo")
public Authentication demo(Authentication a) {
log.info("cusAuth.getAuthorities(): " + cusAuth.getAuthorities());
return a;
}
}

This simply returns the JwtAuthenticationToken.

Public Endpoint

This endpoint is public, its objective is if the user using the frontend is authenticated, we return an object with user specific properties like email, username, and authorities. Otherwise, the returned object is empty. Nidam React Application renders the non-authenticated or authenticated view based on the result of calling this endpoint.

src/main/java/nidam/nidam/controller/DemoController.java
@RestController
public class MeController {
private static final Logger log = Logger.getLogger(MeController.class.getName());

@GetMapping("/me")
public UserInfoDto getMe(Authentication auth) {
if (auth instanceof JwtAuthenticationToken jwtAuth) {
final String email = (String) jwtAuth.getTokenAttributes().getOrDefault(StandardClaimNames.EMAIL, "");

final List<String> roles = auth.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.toList();

final Long exp = Optional.ofNullable(jwtAuth.getTokenAttributes().get(JwtClaimNames.EXP)).map(expClaim -> {
if (expClaim instanceof Long lexp) {
return lexp;
}
if (expClaim instanceof Instant iexp) {
return iexp.getEpochSecond();
}
if (expClaim instanceof Date dexp) {
return dexp.toInstant().getEpochSecond();
}
return Long.MAX_VALUE;
}).orElse(Long.MAX_VALUE);
return new UserInfoDto(auth.getName(), email, roles, exp);
}
return UserInfoDto.ANONYMOUS;
}

/**
* @param username a unique identifier for the resource owner in the token (sub claim by default)
* @param email OpenID email claim
* @param roles Spring authorities resolved for the authentication in the security context
* @param exp seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time when the access token expires
*/
public static record UserInfoDto(String username, String email, List<String> roles, Long exp) {
public static final UserInfoDto ANONYMOUS = new UserInfoDto("", "", List.of(), Long.MAX_VALUE);
}
}
danger

I'm afraid at this point we cannot explain the resource server any further until we cover the Reverse Proxy and BFF on the next pages. We will get back to explaining the resource server after that.