Secure your Spring Boot App using Single Sign On with IBM Security Verify

Gerry Kovan
11 min readMar 25, 2021

Introduction

Security is a critical aspect for both new or existing application. In this blog we will focus on securing a Spring Boot application using Single Sign On with IBM Security Verify as the Identity Provider.

For this example, we will use a sample application called My Favorite Foods. The application allows a user to log in and manage (view, add) their favorite foods. Since a users favorite foods is highly private data, only the authenticated and authorized user should be able to see their own favorite foods and nobody else’s. The architecture of the application is as follows:

Description of the components

IBM Security Verify (ISV): Identity provider responsible for authenticating the user, minting tokens and validating tokens. Acts as the OpenID Provider for authentication as well as the Oauth2 Authorization Server. This is a Sotware as a Service product hosted on the IBM cloud.

Web Client application: Client application that is configured for users (Resource Owner) to log in via the Identity Provider (ISV). In OpenID Connect and OAuth2 terminology, this component is the Relying Party. Makes requests to the Resource Server to retrieve the favorite foods of the logged in user (Resource Owner). This is a Spring Boot microservice and also serves up the UI web pages using the Spring Thymeleaf framework.

Favorite Food Resource server: Application hosting the resources (favorite foods) on behalf of users (Resource Owners). In OpenID Connct and Oauth2 terminology, this component is called the Resource Server. This service is protected and only valid logged in users should be able to access and see their favorite foods.

Relational DB: Relational database that stores the favorite foods for users (Resource Owners). For convenience, we are using an H2 database which is embedded inside the resource server microservice.

Register the Spring Boot app in IBM Security Verify

To setup and run this sample application we need to register an application in IBM Security Verify. The application in Security Verify represents our Spring Boot app. The steps below describe how to set this up:

Step 1: Register for a free trial tenant here: https://www.ibm.com/account/reg/us-en/signup?formid=urx-30041

Here is a link to the IBM Security getting started guide: https://docs.verify.ibm.com/verify/docs/guides

Step2: Login to your IBM Security Verify tenant

Step 3: Create a custom application

From hamburger menu (top left corner), select Applications -> click on Add application and select Custom Application.

Click on “Add Application” to define a new app in IBM Security Verify
Select “Custom Application” and Click the “Add application” button

In the “General” tab, specify any company name (required field).

In the “Sign on” tab:

  • Provide a name for the custom application
  • Specify the sign on method to be “Open ID Connect 1.0”
  • Enter the Application URL: http://localhost:8082/ui
  • Specify Grant type of “Authorization code
  • Set User consent to “Do not ask for consent
  • Specify the value for the redirect URI: http://localhost:8082/ui/login/oauth2/code/custom
  • Uncheck the “Require proof key for code change (PKCE) verification”

Note, the Client id and Client secret will be auto generated when the Save button is eventually clicked.

In the “Sign on” tab token settings, specify JWT as the access token format.

SIgn on token settings configuration

Note, specifying JWT as the format tells IBM Security Verify to return a JWT token when requesting an access token. Otherwise it will return an Opaque token. A JWT token is required in order to protect the resource servers. The resource servers use the JWT token and validate it. We will discuss more about this a bit later.

In the “Sign on” tab Scope and Entitlements section:

- uncheck “Restrict custom scope”.

Uncheck this checkbox to allow app code to specify custom scopes

The Spring Boot Favorite Foods application

This section will describe the key components of our Spring Boot application.

Web Client Application

Here we list the key spring dependencies required to build and secure the application plus integrate it with IBM Security Verify.

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId></dependency><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-webflux</artifactId></dependency><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-oauth2-client</artifactId></dependency><dependency>  <groupId>org.thymeleaf.extras</groupId>  <artifactId>thymeleaf-extras-springsecurity5</artifactId></dependency>

We include the standard spring-boot-starter-web and spring-boot-starter-thymeleaf dependencies for your basic web app to support the UI and REST functionality within the application. Since we are using the new WebClient api (instead of traditional RestTemplate) we included the spring-webflux dependency.

We add the spring-boot-starter-oauth2-client dependency to provide the Spring magic that will integrate with IBM Security Verify (Oauth2 and OIDC Identity provider) in order to perform the authentication and mint the access tokens.

Once you add the spring-boot-starter-oauth2-client dependencies you will need to configure a few things. We create a class called UISecurityConfig and override the configure(HttpSecurity http) method to configure the filter chain. Here we configure the “/” and “/login” endpoints of the web client app to be open and permitted to all users. This is so all users can get to the main page and the login page of the application without authenticating. The oauth2Login() statement specifies that we want to login using an Oauth2 identity provider, in our case IBM Security Verify (ISV)(we will see shortly how we configure that). Note, that for ISV, we had to specify the tokenEndpoint().accessTokenResponseClient(…). If you drill down futher into this code, you will see that we had to create a CustomAccessTokenResponseConverter class to convert the token response from ISV as the Spring Security default implementation did not work with ISV.

In order to integrate with IBM Security Verify (ISV), we had to specify tokenEndpoint().accessTokenResponseClient(…). If you drill down futher into this code, you will see that we had to create and use a new class CustomAccessTokenResponseConverter to convert the token response as the Spring Security default implementation did not work with ISV. The method authorizationCodeTokenResponseClient on line 17 is responsible for calling the token endpoint on the Authorization Server (IBM Security Verify) and returning the token response which contains the JWT access token among other things.

Here is an example of a token response from IBM Security Verify. The access_token is a JWT token which contains the claims of the logged in user.

{   "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlcnZlciJ9.eyJ1bmlxdWVTZWN1cml0eU5hbWUiOiI2NTIwMDE4SFFUIiwiY2xpZW50X2lkIjoiZjNlOTg4OTktNzU4NC00YTM0LWE4NTUtOTEyNjRiOGRlOTdhIiwicmVhbG1OYW1lIjoiY2xvdWRJZGVudGl0eVJlYWxtIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZ2tvdmFuQGhvdG1haWwuY29tIiwiYXVkIjoiZjNlOTg4OTktNzU4NC00YTM0LWE4NTUtOTEyNjRiOGRlOTdhIiwiZ3JhbnRfdHlwZSI6ImF1dGhvcml6YXRpb25fY29kZSIsImFjciI6InVybjppYm06c2VjdXJpdHk6cG9saWN5OmlkOjEiLCJncmFudF9pZCI6ImVmMjFjNThjLWRiYzktNDgwOC1hODI0LTk2N2RkZjM1YTdhNyIsInVzZXJUeXBlIjoicmVndWxhciIsImF1dGhfdGltZSI6MTYxNjE4NjI3OCwic2NvcGUiOiJyZWFkIGdrIG9wZW5pZCB3cml0ZSIsImp0aSI6InNDQmQ2TDFZaUNlbkJodjJzOHd6NUt4N2lPWEQzVSIsImlzcyI6Imh0dHBzOi8vZ2tvdmFuLnZlcmlmeS5pYm0uY29tL29pZGMvZW5kcG9pbnQvZGVmYXVsdCIsInN1YiI6IjY1MjAwMThIUVQiLCJpYXQiOjE2MTYxODY1NTAsImV4cCI6MTYxNjE5Mzc1MH0.PWJDgTTff5lBrpt7ExOhs5emF8pG1t_3DGihG7k54D7AsxU7otv3QCHm3JlyQ8i9y81LhyurQoKkgAioYr8jK0ET8Wc8mr6k3Dhi502PVAhtLPhIGyy2yI0e_O_HzeWRaWGrdw9gAXx-Al1YX5I6cWVshgfZUCuxOP15XSdXvl28vEP8tHDps7ET4UjVAz_IrM4H6sLIudhIn0JEd3psHRrNdmcWV4Exs_Qk2MLq8T9l0AxG-WZIhuRzba-R-1wlQfLOmgtgBfagtW9124FBxoQGT-FOxBan5un4Nf1Nv1BZjOwJlMJ59owWA4_iV_y-NB_QcBmXpDVbO-Bp068xVg","expires_in": 7199,"grant_id": "ef21c58c-dbc9-4808-a824-967ddf35a7a7","id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNlcnZlciJ9.eyJhY3IiOiJ1cm46aWJtOnNlY3VyaXR5OnBvbGljeTppZDoxIiwidXNlclR5cGUiOiJyZWd1bGFyIiwic19oYXNoIjoiRWs0TGNnR3dPSTE4Ql9ReGxMbGtYUSIsInVuaXF1ZVNlY3VyaXR5TmFtZSI6IjY1MjAwMThIUVQiLCJkaXNwbGF5TmFtZSI6IkdlcnJ5IEtvdmFuIiwianRpIjoiSll1Z3ZWY3NBelVZd2tGWDN4S2VKQkdTdHhxdnd3IiwicmVhbG1OYW1lIjoiY2xvdWRJZGVudGl0eVJlYWxtIiwibmFtZSI6IkdlcnJ5IEtvdmFuIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZ2tvdmFuQGhvdG1haWwuY29tIiwibm9uY2UiOiJjeGttY3A2ZmllIiwiZXh0Ijp7InRlbmFudElkIjoiZ2tvdmFuLnZlcmlmeS5pYm0uY29tIn0sImlzcyI6Imh0dHBzOi8vZ2tvdmFuLnZlcmlmeS5pYm0uY29tL29pZGMvZW5kcG9pbnQvZGVmYXVsdCIsImF1ZCI6ImYzZTk4ODk5LTc1ODQtNGEzNC1hODU1LTkxMjY0YjhkZTk3YSIsInN1YiI6IjY1MjAwMThIUVQiLCJpYXQiOjE2MTYxODY1NTAsImV4cCI6MTYxNjE5Mzc1MCwiYXRfaGFzaCI6IjZFOFlCTm9GOHF5SkNPblhpYS15RXcifQ.Ouu1Tt-U0kEzDR8asoMAUQiTqaQL_Vjtn04pICqyCM7rBB3jmOTuAsLIeDsWA7QkKisWTJr5ux4L7unCLf0cRKFuWlEbk-e_XP4QM9boojm9B-GFQue3oDSY4NXTo3dsjnL_IDUhcEWwSwpexpDgrW3wT9vIlvURnj-kqKqaAvX-y3bF89z4nRwBN0R-IzYuw3WuqFdGZLByEVpPl6WdbvXDbQcZWvqoC4tB-n5cN-RZePo2XKbwLZMlpg5fYzOnx6GHinFlrtyXBWcaV7bp5yo_2Y7_ypvnzxAb4_OTUzvLMfXMb_tICLt9dBKllbhruIJIOGz7C6FtbLF-ZlDKMQ","scope": "read openid write","token_type": "jwt"}

Notice that the value for the token_type field is “jwt”. The default implementation in the Spring Security token response converter expects this field to have the value of “Bearer” and that is why we needed to create our custom implementation.

Below is the implementation of the CustomAccessTokenResponseConverter.

You will notice that this code does not check/validate the value of the token_type parameter. Instead on line 14, we just hard code the token type to Bearer set the other other fields accordingly and return the token response object.

Finally, we need to configure the appropriate Spring Security oauth2 parameters in the application.yml file as shown below:

application.yml of the client web app

In the spring.security.oauth2.client.registration.custom namespace, we configure the following properties:

The client-id and client-secret represent the credentials for the client application to get a token from the authorization server which is IBM Security Verify. We pass in the values from the environment when we start the spring boot client web app. More on this later.

client-id: Auto generated by the Application you defined in IBM Security Verify. Value injected from environment when spring boot web app starts.

client-secret: Auto generated by the Application you defined in IBM Security Verify. Value injected from environment when spring boot web app starts.

authorization-grant-type: Specify authorization_code as that is the most secure oauth2 flow for web apps.

redirect_uri: This is also known as the callback URI, In the authorization_code flow. During the login process, assuming the user authenticated successfully, the The authorization server (ISV) creates a short lived auth code and responds with an http redirect that the users browser executes in order to exchange the short lived auth code with an access token. The redirect_uri must be registered with the application defined in ISV during the app registration process as described above for security purposes. The redirect uri invokes the web client app (relying party) with the short lived auth code. The web client app, then makes a call to IBM Security Verify tenant token endpoint with the short lived auth code and the full credentials that includes the client id, client secret and several other parameters to get a valid token. The key idea here is that because the web client app is a server component, it can store the sensitive credential (client secret) securely in order to exchange the auth code with a valid access token.

In the spring.security.oauth2.client.provider.custom namespace we configure the following properties:

authorization-url: url of the authorization server

token-uri: Uri of the token endpoint on the authorization server

user-info-uri: Uri of the userinfo endpoint on the authorization server

issuer-uri: Defines the base uri of the authorization server.

jwk-set-uri: Uri of the JWKS (JSON Web Key Set) endpoint on the authorization server. This endpoint contains the public key counterparts of the private keys which were used to sign the tokens during token minting/creation.

Note: You will need to replace https://gkovan.verify.ibm.com with your specific IBM Security Verify tenant. You can get a trial tenant for free.

WebClient Configuration

The web client application is using the Spring Webflux WebClient class to make REST calls to the resource server to get the logged in users favorite foods. The code below shows how the WebClient is configured. Line 4 configures the WebClient to autamically inject the http Authorization header containing the Bearer access token. Without a valid Bearer access token, the resource server will not execute the request and instead return an HTTP 401 error.

Below you can see the logs from the web client application showing the http GET request to the resource server. The logs show both the URL and the header which containers the Authorization Bearer token.

### WebClient REQUEST: ### GET http://localhost:8081/sso-resource-server/api/favorite-food### WebClient HEADER ### Authorization=Bearer eyJhbGciOiJSUzI1...

Favorite food resource server

Now we will see how the resource server that processes the request from the web client application to return the favorite foods of the logged in user.

The key dependencies in the maven pom file are:

<!-- web --><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId></dependency><!-- security --><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><!-- persistence --><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency>  <groupId>com.h2database</groupId>  <artifactId>h2</artifactId></dependency>

We include the standard spring-boot-starter-web dependency to support the web REST functionality within the application.

We add the spring-boot-starter-oauth2-resource-server dependency to provide the Spring magic that will secure the microservice endpoints.

We also have two persistence dependencies, spring-boot-starter-data-jpa and h2 to support the persistence capabilities of the microservice app since it needs to connect to a relational database.

To configure the resource we extend the Spring Security WebSecurityConfigurerAdapter class as follows:

The code above leverages the Spring Security DSL to configure various security settings like which api routes are open/unprotected, which routes are protected and what scopes or permissions are required plus the authentication mechanism, in our case jwt.

Line 13, specifies the unprotected api routes that don’t need an Authentication bearer token header to be present.

Line 14 and 15, specifies that the “/favorite-foods” route needs to be authenticated and have a “read” scope present in the authentication token. Line 19 specifies that the routes listed on lines 14 and 16 need to be authenticated.

Line 21 and 22, specify the types of tokens supported, in our case it is JWT.

The application.yml for the resource server configuration is as follows:

Line 7 specifies the uri location of the Identity provider that issues/mints the JWT tokens. In our example, this is the IBM Security Verify tenant.

Line 8 specifies the jwk uri, which is a public open endpoint that contains the public key data that the resource server uses to validate the jwt token. Not, when a JWT token is created, the token gets signed using a private key. This public key is the corresponding key to that private key.

The controller endpoint for “/api/favorite-foods” is as follows:

On line 2, The “@AuthenticationPrincipal Jwt principal” argument is auto injected into the controller by Spring from the security context. The principal object essentially contains the claims that were unpacked from the jwt access token. From here, we can retrieve the logged in user information. On line 4, we populate the “loggedInUser” variable from the principal. Line 8 to 10 filter the results so it only has the favorite foods of the logged in user. Lines 12 to 14 populated the data trasfer object for the response.

The Spring magic occurs via filters. Before execution reaches the Controller, the spring filters will ensure that the security policies configured in the SecurityConfig class and application.yml are enforced and that a valid jwt token exists with the proper scope and any other claims.

Running the application

All the source code can be found in this git repo: https://github.com/gkovan/IBM-Security-Verify-Spring-Boot-SSO

You will need to create a IBM Security Verify tenant and register an application as we described above.

Then you need to start the two microservices found in the git repo.

To start the client web app:

mvn spring-boot:run -Dspring-boot.run.arguments="--CLIENT_ID=<your client id> --CLIENT_SECRET=<your client secret>"

The client id and client secret is auto generated by IBM Security Verify when you register your application.

To start the resource server:

mvn spring-boot:run

Open a browser and to to the url: http://localhost:8082/ui

Click the Login button to login as a user. You will be redirected to the login screen. Once you login, click on the “My favorite foods” link. This will show the my favorite foods for the currently logged in user:

Before you log into the application, you will first need to create a user in IBM Security Verify. Login to the ISV tenant that you created and click on the hamburger menu (top left corner) and click on Users and Groups. Then click on the Add User button. For the identity source, select Cloud Directory and fill in the rest of the details such as your name, email etc. and follow the guided process.

Note: To populate the database manually, edit the file https://github.com/gkovan/IBM-Security-Verify-Spring-Boot-SSO/blob/main/sso-resource-server/src/main/resources/data.sql. Update the name field and replace it with the email address for the user that you just created above.

When the resource server starts up, the H2 database gets populated automatically from the data in this file.

References:

--

--

Gerry Kovan

IBMer, software engineer, Canadian living in New York, husband, father and many other things.