Secure a Spring Boot REST API with JSON Web Token

Plus references to Angular integration

Nouhoun Y. Diarra
Better Programming

--

In this piece, I am going to walk you through how to secure a Spring Boot REST API with JSON Web Token (JWT) to exchange claims between a server and a client. This is Part two of a collaborative effort between my colleague Julia Passynkova and myself demonstrating how to secure an Angular 2+ application using Spring Boot as a RESTful back end. If you are impatient or prefer to learn directly from the code, check out the code in the Github repository. You can find the Angular integration project here.

A Quick Intro

Over the past few years, Spring Boot has greatly simplified the configuration of Spring Framework applications. The unceremonious approach it takes lets developers enable basic security for an application by simply having Spring Security dependency on the classpath.

Choosing JWT to secure your API endpoints is a great choice because it ensures a stateless exchange of tokens between the client and the server, and is compact and URL-safe. With JWT, it is not necessary to store access tokens in a database (although you may still do that and even need to depending on the use case) or worry about sticky sessions. This makes building redundancy into your enterprise application more cost-effective, at least as far as the security aspect is concerned. You do, however, need to deal with other aspects such as token revocation, but that is not covered here. The basis for understanding how useful JWT is is to first grasp OAuth 2.0. For a quick reference, below is an illustration of the OAuth dance.

Illustration of OAUTH 2. dance

What you will need for the project?

1. Spring Boot 1.5.3.RELEASE project with either Maven or Gradle. This project uses Maven.

2. The following dependencies:

  • spring-boot-starter-web
  • spring-boot-starter-security
  • spring-boot-starter-data-jpa
  • spring-boot-starter-web
  • com.h2database — H2 Database for storing sample test data
  • spring-security-jwt version: 1.0.7.RELEASE
  • spring-security-oauth2 version: 2.1.0.RELEASE

Final project structure

When you clone the project from Github and import it into your IDE, for instance, its structure will look similar to the following. Here, I am using a screenshot from IntelliJ Community Edition IDE. Yours will look slightly different depending on the IDE you use.

1. Configure Spring Security

Thanks to Spring Boot’s autoconfiguration we will need minimal customizations to get set:

First, let’s look at the application’s configuration file, application.properties:

Then the security configuration class:

  • @EnableWebSecurity: Enables spring security and tells Spring Boot to apply all the sensitive defaults
  • @EnableGlobalMethodSecurity: Allows us to have method level access control
  • JwtTokenStore and JwtAccessTokenConverter beans: A JwtTokenStore bean is needed by the authorization server and to enable the resource server to decode access tokens a JwtAccessTokenConverter bean must be used by both servers. In this case, we are providing a symmetric key signing.
  • UserDetailsService: We inject a custom implementation of UserDetailsService, named AppUserDetailsService (see the code on GitHub for details) in order to retrieve user details from the database.
  • Encoding: SHA-256 is used to encode passwords. This is set in encodingStrength property
  • Realm: The security realm name is defined in securityRealm property. This name is arbitrary. A realm is basically all that define our security solution from the provider to roles, users, etc.

2. Configure Authorization Server

  • @EnableAuthorizationServer: Enables an authorization server
  • AuthorizationServerConfig which is our authorization server configuration class extends AuthorizationServerConfigurerAdapter which in turn is an implementation of AuthorizationServerConfigurer. The presence of a bean of type AuthorizationServerConfigurer simply tells Spring Boot to switch off auto-configuration and use the custom configuration. Also, the AuthorizationServerConfig, like any other configuration class, has its definition automatically scanned, wired, and applied by Spring Boot because of the @Configuration annotation.
  • Client id: defines the id of the client application that is authorized to authenticate, the client application provides this in order to be allowed to send a request to the server.
  • Client secret: is the client application’s password. In a non-trivial implementation client ids and passwords will be securely stored in a database and retrievable through a separate API that clients applications access during deployment. These pieces of information can also be shared and stored in environment variables although that would not be my preferred option.
  • Grant type: we define the grant type password here because it’s not enabled by default
  • The scope: read/write defines the level of access we are allowing to resources
  • Resource Id: The resource id specified here must be specified on the resource server as well
  • AuthenticationManager: Spring’s authentication manager takes care of checking user credential validity
  • TokenEnhancerChain: We define a token enhancer that enables chaining multiple types of claims containing different information

3. Configure the Resource Server

@EnableResourceServer: Enables a resource server. By default, this annotation creates a security filter which authenticates requests via an incoming OAuth2 token. The filter is an instance of WebSecurityConfigurerAdapter which has an hard-coded order of three (Due to some limitations of Spring Framework). You need to tell Spring Boot to set the OAuth2 request filter order to three to align with the hardcoded value. You do that by adding security.oauth2.resource.filter-order = 3 in the application.properties file. Hopefully, this will be fixed in future releases.

The resource server has the authority to define the permission for any endpoint. The endpoint permission is defined with:
.antMatchers(“/actuator/**”, “/api-docs/**”).permitAll()
.antMatchers(“/springjwt/**”).authenticated()

Notice here that the resource and the authorization servers both use the same token service. That is because they are in the same code base so we are reusing the same bean.

4. Configure a Data Source

Spring Boot can fully auto-configure the in-memory H2 data source once it’s defined on the classpath. However, to give you a better sense of how you can take control of your application and customize your data source the following configuration is provided:

Replace H2 with any database (MariaDB, MySQL, Oracle, SQL Server, etc.) to fit your use case.

5. Database Scripts and Test Data

schema.sql

data.sql

6. Entities

User, Role, RandomCity entities are created to map to the data model

7. Exposing Resources via a REST Controller

Here two endpoints are exposed

  • /springjwt/cities: This endpoint is accessible to all authenticated users
  • /springjwt/users: This endpoint is accessible only to an admin user

8. Running and Testing the Application

First, you will need the following basic pieces of information:

Step 1: Generate an access token

Use the following generic command to generate an access token: $ curl client:secret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=pwd

For this specific application, to generate an access token for the non-admin user john.doe, run: $ curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=jwtpass. You'll receive a response similar to below:

`
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiYWRtaW4uYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk0NDU0MjgyLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF9VU0VSIiwiQURNSU5fVVNFUiJdLCJqdGkiOiIwYmQ4ZTQ1MC03ZjVjLTQ5ZjMtOTFmMC01Nzc1YjdiY2MwMGYiLCJjbGllbnRfaWQiOiJ0ZXN0and0Y2xpZW50aWQifQ.rvEAa4dIz8hT8uxzfjkEJKG982Ree5PdUW17KtFyeec",
"token_type": "bearer",
"expires_in": 43199,
"scope": "read write",
"jti": "0bd8e450-7f5c-49f3-91f0-5775b7bcc00f"
}`

Step 2: Use the token to access resources through your RESTful API

Use the token to access resources through your RESTful API

To access content available to all authenticated users:

  • Use the generated token as the value of the Bearer in the Authorization header as follows: curl http://localhost:8080/springjwt/cities -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiYWRtaW4uYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk0NDU0MjgyLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF9VU0VSIiwiQURNSU5fVVNFUiJdLCJqdGkiOiIwYmQ4ZTQ1MC03ZjVjLTQ5ZjMtOTFmMC01Nzc1YjdiY2MwMGYiLCJjbGllbnRfaWQiOiJ0ZXN0and0Y2xpZW50aWQifQ.rvEAa4dIz8hT8uxzfjkEJKG982Ree5PdUW17KtFyeec"
  • The response will be: [ { "id": 1, "name": "Bamako" }, { "id": 2, "name": "Nonkon" }, { "id": 3, "name": "Houston" }, { "id": 4, "name": "Toronto" }, { "id": 5, "name": "New York" }, { "id": 6, "name": "Mopti" }, { "id": 7, "name": "Koulikoro" }, { "id": 8, "name": "Moscow" } ]

To access content available only to an admin user:

As with the previous example, first, generate an access token for the admin user with the credentials provided above then run curl http://localhost:8080/springjwt/users -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiYWRtaW4uYWRtaW4iLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk0NDU0OTIzLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF9VU0VSIiwiQURNSU5fVVNFUiJdLCJqdGkiOiIyMTAzMjRmMS05MTE0LTQ1NGEtODRmMy1hZjUzZmUxNzdjNzIiLCJjbGllbnRfaWQiOiJ0ZXN0and0Y2xpZW50aWQifQ.OuprVlyNnKuLkoQmP8shP38G3Hje91GBhu4E0HD2Fes"

The result will be: [ { "id": 1, "username": "john.doe", "firstName": "John", "lastName": "Doe", "roles": [ { "id": 1, "roleName": "STANDARD_USER", "description": "Standard User - Has no admin rights" } ] }, { "id": 2, "username": "admin.admin", "firstName": "Admin", "lastName": "Admin", "roles": [ { "id": 1, "roleName": "STANDARD_USER", "description": "Standard User - Has no admin rights" }, { "id": 2, "roleName": "ADMIN_USER", "description": "Admin User - Has permission to perform admin tasks" } ] } ]

9. Access a non-secure or non-protected endpoint

The http://localhost:8080/health is not restricted. This is accessible to anonymous users and can be used for load balancing health check purpose.

References & Useful Readings:

--

--