/images/resources/tutorials/migrations/tutorials-spring.jpg

Securing a Spring Boot API with JWTs

On this page

Spring Security has evolved a lot regarding OAuth2 and OIDC. This tutorial shows how to let the framework validate a JWT and make use of claims in your API. Spring will validate the token and make sure the correct scope is used for the specific endpoint. The API can use the claims in the JWT to make additional decisions.

Note

Curity Identity Server is used in this example, but other OAuth servers can also be used.

Prerequisites

This tutorial covers the API side, meaning that how to obtain a JWT is out of scope.

Please look at the separate tutorial OIDC Client with Spring Security for information how to create a client using Spring Boot.

We assume the JWT contains the following scope, services:read and the following claim, role=developer or role=something else.

Create a Spring Boot Application

We will create a simple Spring Boot application from scratch using Spring Initialzr, a website helping you to create a new Spring Boot application with the necessary dependencies.

Open start.spring.io in your browser to access Spring Initialzr.

In the configuration window that opens, select gradle, enter io.curity.example for the name of the group and call the artifact secureapi.

Search for and add the following dependencies:

  • Spring Web
  • OAuth2 Resource Server
Spring Initializr

Generate the application. Spring Initializr creates an archive with a bootstrap application that includes the selected dependencies. Download and extract the archive, and import the project in an IDE of your choice.

More Dependencies

We have to add an extra dependency for us to be able to work with the JWT. Open build.grade and add implementation 'org.springframework.security:spring-security-oauth2-jose'.

Adding a Controller

Now it is time to create our controller. Create the file src/main/java/io/curity/example/secureapi/ExampleController.java.

Paste the following in the file:

java
1234567891011121314151617181920212223242526272829
package io.curity.example.secureapi;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class ExampleController {
@GetMapping("/services")
public List<String> jwtProtected(@AuthenticationPrincipal Jwt jwt) {
String role = jwt.getClaimAsString("role");
return getServices("developer".equals(role));
}
private List<String> getServices(boolean developer) {
List<String> services = new ArrayList<>();
services.add("https://localhost/service/email");
services.add("https://localhost/service/support");
if (developer) {
services.add("https://localhost/service/devportal");
}
return services;
}
}

This will create an endpoint /services. It will return a list of URLs. If your JWT has a claim role set to developer you will have an extra URL returned.

Use Spring Security to Authorize the Request

For this tutorial the OpenID Connect metadata of the Curity Identity Server must be published. We will make use of the location of the OpenID Connect Provider to load the essential configuration for the integration. The Curity Identity Provider publishes the metadata at {issuerUri}/.well-known/openid-configuration. We define the issuer URI as follows:

Parameter NameValue in tutorial
OpenID Provider Issuer Urihttps://idsvr.example.com/oauth/v2/oauth-anonymous

With this URL Spring Security will be able to find the public key to be able to validate the JWTs issued.

Make sure Curity Identity Server is up and running

Your Spring application will fetch the public key when starting up an will fail if there is no response.

Rename src/main/resources/application.properties to application.yml and paste this into it:

123456
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idsvr.example.com/oauth/v2/oauth-anonymous

Then we create the src/main/java/io/curity/example/secureapi/SecurityConfiguration.java and paste:

java
123456789101112131415161718192021222324252627282930
package io.curity.example.secureapi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.jwt.JwtDecoders;
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
String issuerUri;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/services").hasAuthority("SCOPE_services:read")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2ResourceServer ->
oauth2ResourceServer
.jwt(jwt ->
jwt.decoder(JwtDecoders.fromIssuerLocation(issuerUri))
)
);
}
}

This configuration will make sure that all requests are authenticated. It will also make sure that calls to /services have the scope services:read. Spring appends SCOPE_ to all scopes and add them as an authority.

Run the Demo Application

Start the Application in your IDE or by running ./gradlew bootRun. The default configuration will start a listener on localhost:8080.

Truststore issues

The Curity Identity Server ssl certificate must be trusted, otherwise your API will fail to introspect the token. Look at the jvm arguments -Djavax.net.ssl.trustStore and -Djavax.net.ssl.trustStorePassword on how to configure your trust store.

Get a JWT from your Curity Identity Server.

Test the API with your favorite tool. In cURL it would look something like this: curl --request GET 'http://localhost:8080/services' \ --header 'Authorization: Bearer eyJraWQiOi...Gba'

Expected out is ["https://localhost/service/email","https://localhost/service/support","https://localhost/service/devportal"]if you have the role claim set to developer.

Conclusion

By using Spring Security developing your APIs you can setup a convenient configuration for your authorization. The authorization decisions can be made base on both scopes and properties in claims.

In this tutorial we used the authority concept of Spring Security. Another concept worth looking into is role.

For further examples and help regarding OAuth2 and Spring Security visit Spring Security Reference.

Other tutorials regarding how to integrate Curity Identity Server with the Spring framework: