Lab 1: The server side (resource server)
Tip: You may look into the Spring Boot Reference Documentation and the Spring Security OAuth2 Resource Server Reference Documentation on how to implement a resource server.
Learning Targets
In this lab you will learn:
Authentication using JSON Web Tokens (JWT) sent via HTTP header
Validation of the JWT (Signature and expiry date/time) with loading public key for validating
Authorize users by using the roles claim inside the access token
Step 1: Explore the existing server application
To start with this tutorial please navigate to the project labs/initial/product in your IDE.
This is the starting point for all following implementation steps.
The existing product server is using the base spring security lib to secure its endpoints using basic authentication and form login.
Before diving into changing the server application into a resource server it is recommended to explore the existing application. The application exposes several API endpoints. All endpoints are documented by OpenAPI 3.
To start the product server select the class com.example.ProductApplication and run this in your IDE.
You can find the Swagger documentation at http://localhost:9090/server/swagger-ui/index.html. The corresponding OpenAPI JSON doc can be found at http://localhost:9090/server/v3/api-docs.
You may also directly access the API endpoints for retrieving protected resources:
If you call the http://localhost:9090/server/v1/products (products endpoint) then you will get the following result:

If you call the users endpoint then you will get these results:

Both endpoints are secured by basic authentication or form based login. you can access the endpoints by using the following user credentials (access for users list requires ADMIN role):
bruce.wayne@example.com/wayne
USER
clark.kent@example.com/kent
USER
peter.parker@example.com/parker
ADMIN, USER
To make accessing the APIs more convenient you can use the provided postman collection. Please check the setup section for details.
Now let's start with changing the server into a resource server using modern authentication with a JSON web token (JWT).
Step 2: Change Maven dependencies for resource server
To change these existing authentication mechanisms to JWT authentication as a resource server we need to adapt the spring security dependencies, i.e. use the corresponding one for building a secure OAuth2/OIDC resource server instead of simple basic authentication.
To perform this required change replace the following dependency in the existing maven pom.xml file:
pom.xml:
with this new dependency:
pom.xml:
Step 3: Add required properties for resource server
The resource server requires the public key(s) to validate the signature of incoming JSON web tokens (JWT). This way nobody can just issue their own JWT tokens or modify the issued token along the transmission path. The public key(s) will be automatically grabbed from the JSON web key set provided by the identity provider at http://localhost:9000/oauth2/jwks.
This endpoint publishes the public key(s) that are each identified by their key id (kid).
Whenever the resource server gets a JSON Web Token (JWT) with a kid in the header part (like the following sample), then Spring Security fetches the public key with the matching key id from this JWKS endpoint, caches it (so it does not have to load it for every request) and validates the JWt with the loaded public key.
JWT Header:
JWT Payload:
Spring security provides a predefined property spring.security.oauth2.resourceserver.jwt.jwt-set-uri to specify this JWKS endpoint for loading and caching public keys.
After adding this new property the updated application.yml should look like this:
Application.yml:
Important: Please check that all indents in the yaml file are correct. Otherwise, you may get strange runtime errors when starting the application.
Step 4: Change security configuration for resource server
Please navigate to the class com.example.security.WebSecurityConfiguration in your IDE and change this with the following contents.
com.example.security.WebSecurityConfiguration.java:
In this updated security configuration (the second SecurityFilterChain called api(...)) we:
disable web sessions as with token authentication each request must contain the token in the header and a session cookie is not required any more (stateless authentication)
disable CSRF protection as we do not use session cookies anymore and therefore are not vulnerable for CSRF attacks
disable basic authentication and formula based login (we only support token based authentication now)
enable the application to act as an OAuth2/OIDC resource server requiring JWT as bearer tokens in the authorization header
Please note that the bean definition for the PasswordEncoder has been removed as well as the password encoding is not required any more.
This will cause compilation errors in ProductInitializer class. To solve these just remove all references to the encoder in that class. There is also no need anymore to create users as users are not required any more to log in. This will be achieved by the OAuth authorization server (only this component knows about user credentials and other user details).
com.example.ProductInitializer.java:
Step 5: Convert the JWT into the ProductUser object
With the changes of step 3 the base configuration for a resource server is set up. But there is one issue with this change. In class com.example.product.ProductRestController we do not get ProductUser as input for @AuthenticationPrincipal.
Instead, by default the class org.springframework.security.oauth2.jwt.Jwt will be provided as input (as this is the standard authenticated principle object when JWT is used in spring security).
To change this behavior we have to add our own converter from the JWT token to the ProductUser class. This is done in several steps.
First we need to define our own type of the interface AuthenticationToken. This is the central point where Spring Security stores all authentication details after authentication has been successfully performed.
For convenience spring security provides the AbstractAuthenticationToken that implements most parts of AuthenticationToken.
com.example.security.ProductUserAuthenticationToken:
The previous class will now be used as part of the ProductJwtAuthenticationConverter. This converts contents of the JWT token into attributes of our ProductUser.
com.example.security.ProductJwtAuthenticationConverter:
Please note: The existing ProductUserDetailsService class is not required any more and is replaced by the
ProductJwtAuthenticationConverterabove. So this class can be deleted completely.
Finally, we have to add this new ProductJwtAuthenticationConverter to the security configuration.
com.example.security.WebSecurityConfiguration.java:
Step 6: Run the product server application
Now we are ready to re-start the product server. Select the class com.example.ProductApplication and run this (use the right mouse button in your IDE or the spring boot dashboard if applicable).
To test the REST Api http://localhost:9090/server/products of the running product server we will use Postman. You may also use command line tools like curl or httpie as well.
If you have imported the postman collection as described in the setup section then the authorization part should be pre-filled.

These are the required values that should be already configured in the Authorization tab:
Grant Type
Authorization Code with PKCE
Authorization URL
https://localhost:9000/oauth/authorize
Access Token URL
https://localhost:9000/oauth/token
Client ID
demo-client-pkce
All requests of the postman collection inside the folder OAuth2 Bearer Token require a valid JWT. So if you perform such request you will get a 401 error because the JWT token is missing to access this endpoint.
To get such a token click on the folder OAuth2 Bearer Token, then navigate to the tab Authorization click on the Get New Access Token button.
After you got a token click proceed and then click on _Use Token_try again to send the request. This time it should work, and you should see a list of products as JSON response.
In the next step we will make accessing the backend service a bit more user-friendly by enabling a provided client frontend to retrieve products from the backend using OAuth2/OIDC access tokens.
Last updated