Hello! Welcome back to our course on production-ready microservices.
In the previous lesson, we successfully built and configured a standalone Spring Authorization Server. You now have a server capable of authenticating users and issuing signed JSON Web Tokens (JWTs). This is the security foundation of our microservices ecosystem.
Today, we'll build the other half of this security pairing. Our learning outcome is to secure a microservice by configuring it as an OAuth 2.0 Resource Server to validate JWT access tokens. We will create a simple Spring Boot microservice and teach it how to inspect the JWTs issued by your authorization server, validate them, and grant or deny access to its protected APIs based on their contents.
This is a fundamental pattern for securing stateless microservices and a topic you can certainly expect to discuss in-depth during interviews for senior engineering roles.
The Role of the Resource Server
Let's revisit our architecture. The Authorization Server acts as the central security authority, like a passport office. The Resource Server is like the border control agent for a specific country (your microservice). It doesn't issue passports, but it must be an expert at verifying them—checking the signature, the expiration date, and whether the passport holder is authorized to enter.
The following diagram illustrates the key components within a Resource Server that handle this validation process.

Our focus today is to configure these components in a Spring Boot application.
1. Setting Up the Resource Server Project
Let's start by creating a new Spring Boot microservice. You can use start.spring.io or your IDE.
- Project: Maven or Gradle
- Language: Java
- Spring Boot: 3.x or later
- Java: 17 or later
- Dependencies:
Spring WebSpring Boot Starter OAuth2 Resource Server
The key dependency is spring-boot-starter-oauth2-resource-server. It bundles all the necessary Spring Security modules to configure your application to validate bearer tokens.
OAuth 2.0 Resource Server With Spring Security
The Baeldung article "OAuth 2.0 Resource Server With Spring Security" provides a concise overview of the necessary dependencies and the project structure. We will follow a similar approach.
Please read section 4, "Resource Server – Using JWTs," specifically sub-sections 4.1 to 4.3. This will familiarize you with the dependencies and the simple REST controller we'll be securing.
After setting up the project, create a simple RestController as shown in the article, for example, a FoosController with a GET endpoint. This will be the resource we protect.
2. Configuring JWT Validation
This is the core of our lesson. Spring Boot provides powerful auto-configuration that makes setting up a resource server remarkably simple.
The Magic of issuer-uri
The most straightforward way to configure your resource server is by telling it where your authorization server is. You do this by setting a single property in your application.properties or application.yml file.
application.properties
# The port for our new microservice
server.port=8081
# Point to the issuer URI of the Authorization Server you built
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9000
(Remember to use the correct port and path for the Authorization Server you created in the last lesson.)
This one line triggers a series of actions by Spring Security's auto-configuration, a process interviewers often probe to test your depth of knowledge.
OAuth 2.0 Resource Server JWT :: Spring Security
The official Spring Security documentation explains exactly what happens when you provide the issuer-uri. Understanding this discovery process is crucial for troubleshooting and demonstrates a deeper understanding than just knowing the property name.
Please read the sections "Minimal Configuration for JWTs", "Startup Expectations", and "Runtime Expectations". Focus on how the resource server uses the issuer-uri to find the jwks_uri and what claims it validates by default.
As you've just read, providing the issuer-uri causes the Resource Server to:
- On startup, contact the issuer at a standard OpenID Connect discovery endpoint (e.g.,
http://localhost:9000/.well-known/openid-configuration). - Parse the response to find the
jwks_uri(the URL for the JSON Web Key Set). - Configure a
JwtDecoderbean that knows how to fetch the public keys from thatjwks_uri. - At runtime, when a JWT is received, the
JwtDecoderverifies the token's signature using the appropriate public key. It also validates theiss(issuer),exp(expiration), andnbf(not before) claims.
Trade-off: The major trade-off with this approach is that your Resource Server's startup is now dependent on the Authorization Server being available. If it can't reach the discovery endpoint, it will fail to start.
An alternative is to specify the JWK Set URI directly, which decouples the startup process:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:9000/oauth2/jwks
With this, you still typically want to provide the issuer-uri so that the iss claim is still validated, but the startup dependency is removed.
3. Enabling the Resource Server Security Filter Chain
Just adding the property isn't enough. We also need to tell Spring Security to activate the resource server features in our security configuration.
Create a new configuration class, SecurityConfig.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.GET, "/foos/**").hasAuthority("SCOPE_read")
.requestMatchers(HttpMethod.POST, "/foos").hasAuthority("SCOPE_write")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
}
Let's break down this configuration:
.authorizeHttpRequests(...): This is standard Spring Security. We are defining our authorization rules. Here, we specify thatGETrequests to/foos/**require an authority ofSCOPE_read..oauth2ResourceServer(oauth2 -> oauth2.jwt(...)): This is the key line. It enables OAuth 2.0 Resource Server support and configures it to accept JWT-encoded bearer tokens. It automatically wires up theBearerTokenAuthenticationFilterto extract the token and theJwtAuthenticationProviderto process it using theJwtDecoderwe configured via properties.
The video below walks through a very similar configuration, which can help solidify your understanding of how these pieces fit together.
Spring Boot 3 Keycloak OAuth 2 Tutorial with Spring Security
This clip from Programming Techie demonstrates configuring a resource server from scratch. Although the authorization server is Keycloak, the resource server configuration principles are identical to what we're doing.
Watch from 43:30 to 48:18. Pay attention to how the SecurityFilterChain bean is created and how oauth2ResourceServer().jwt() is used to enable JWT validation. Also, note the application.properties configuration which is very similar to ours.
Why SCOPE_read?
By default, Spring's JwtAuthenticationConverter inspects the scope claim in the JWT. If it finds a scope like "read", it creates a GrantedAuthority with the prefix SCOPE_. This is why we use .hasAuthority("SCOPE_read"). This prefixing behavior is configurable, which is a common customization point in real-world applications.
Test your understanding!
A common security requirement is to ensure a JWT was intended for your specific service. The standard JWT claim for this is aud (Audience). How would you configure your resource server to reject any token that does not have https://api.my-service.com in its aud claim? (Hint: Look in your application.properties file).
Show answer
You would add the audiences property to your application.properties file:
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9000
spring.security.oauth2.resourceserver.jwt.audiences=https://api.my-service.com
Spring Security will then automatically add a validator that checks if the aud claim in the incoming JWT contains this value. If not, the token will be considered invalid. You can find more on this in the official Spring Security documentation under the section "Supplying Audiences".
4. Testing the Complete Flow
Now it's time to see everything working together.
- Start your Authorization Server from the previous lesson.
- Start your new Resource Server application.
- Get an Access Token: Use Postman or
curlto perform theauthorization_codeflow (or any other flow you configured, likeclient_credentials) against your Authorization Server. Make sure to request the scopes you need (e.g.,read,write). You should receive a JSON response containing anaccess_token. - Access the Protected Resource:
- Create a new request in Postman to your resource server's endpoint (e.g.,
GET http://localhost:8081/foos/1). - Go to the "Authorization" tab, select "Bearer Token", and paste the
access_tokenyou received. - Send the request. If your token is valid and contains the
readscope, you should get a200 OKresponse.
- Create a new request in Postman to your resource server's endpoint (e.g.,
- Test Failure Cases:
- Try accessing the endpoint without any token (you should get a
401 Unauthorized). - Try using a token that doesn't have the required scope (e.g., call the
POSTendpoint with a token that only has thereadscope). You should get a403 Forbidden.
- Try accessing the endpoint without any token (you should get a
Conclusion
Excellent work! You have now secured a microservice using industry-standard OAuth 2.0 and JWT. You've gone beyond just knowing the theory and have a practical, working implementation of a core microservices security pattern.
Key Takeaways:
- A Spring Boot application becomes a Resource Server by including the
spring-boot-starter-oauth2-resource-serverdependency. - The
spring.security.oauth2.resourceserver.jwt.issuer-uriproperty enables powerful auto-configuration for JWT validation by discovering the authorization server's public keys. - The
oauth2ResourceServer().jwt()DSL activates the necessary security filters to process bearer tokens. - You can protect endpoints using standard Spring Security methods like
hasAuthority(), which map to claims (likescope) inside the JWT.
Next Up
In our current setup, each microservice is responsible for validating JWTs. This is a valid approach, but in many architectures, this cross-cutting concern is centralized at the edge of the network.
In our next lesson, we will explore this alternative pattern: Implement JWT token validation at the API Gateway for centralized authentication. We will see how an API Gateway can act as a gatekeeper for your entire system, validating tokens once before forwarding requests to the appropriate downstream service.
Can't find a good explanation? Sign up and we'll make it for you
Sign up