Create a Secure Spring REST API
Heads up... this blog post is old!
For an updated version of this blog post, see Build a Secure Spring Data JPA Resource Server.
“If it is useful, it will be modified.” Those words of wisdom came from a QA teacher of mine, to explain that all software evolves when it becomes useful to someone, and for as long as it is useful. We all know this. Users ask us for new features, bug fixes and changes in domain logic every day. As any project (especially a monolith) grows it can begin to become difficult to maintain, and the barrier to entry for anyone new just gets higher and higher. In this tutorial, I’m excited to walk you through building a secure Spring REST API that tries to solve for some of these pain points using a microservices architecture.
In a microservices architecture, you logically divide your application into several apps that can be more easily maintained and scaled, use different stacks, and support more teams working in parallel. But microservices are the simple solution to every scaling and maintenance problem.
Microservices also present a number of architectural challenges that must be addressed:
- How those services communicate?
- How should communication failures and availability be handled?
- How can a user’s requests be traced between services?
- And, how should you handle user authorization to access a single service?
Let’s dig in and find out how to address these challenges when building a Spring REST API.
Secure Your Spring REST API with OAuth 2.0
In OAuth 2.0, a resource server is a service designed to handle domain-logic requests and does not have any kind of login workflow or complex authentication mechanism: it receives a pre-obtained access token that guarantees a user have grant permission to access the server and delivers the expected response.
In this post, you are going to build a simple Resource Server with Spring Boot and Okta to demonstrate how easy it is. You will to implement a simple Resource Server that will receive and validate a JWT Token.
Add a Resource Server Your Spring REST API
This example uses Okta to handle all authentication process. You can register for a free-forever developer account that will enable you to create as many users and applications you need.
I have set up some things so we can get started easily. Please clone the following resource repository and go to startup
tag, as follows:
git clone -b startup https://github.com/oktadeveloper/okta-secure-spring-rest-api-example secure-spring-rest-api
cd secure-spring-rest-api
This project has the following structure:
$ tree .
.
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── net
│ │ └── dovale
│ │ └── okta
│ │ └── secure_rest_api
│ │ ├── HelloWorldController.java
│ │ ├── SecureRestApiApplication.java
│ │ └── SecurityConfig.java
│ └── resources
│ └── application.properties
└── test
└── java
└── net
└── dovale
└── okta
└── secure_rest_api
└── SecureRestApiApplicationTests.java
14 directories, 9 files
I created it using the excellent Spring Initializr and adding Web
and Security
dependencies. Spring Initializr provides an easy way to create a new Spring Boot service with some common auto-discovered dependencies. It also adds the Maven Wrapper: so you use the command mvnw
instead of mvn
, the tool will detect if you have the designated Maven version and, if not, it will download and run the specified command.
Fun fact: Did you know the Maven wrapper was originally created by Okta’s own Brian Demers?!
The file HelloWorldController
is a simple @RestController
that outputs “Hello World”.
In a terminal, you can run the following command and see Spring Boot start:
mvnw spring-boot:run
TIP: If this command doesn’t work for you, try ./mvnw spring-boot:run
instead.
Once it finishes loading, you’ll have a REST API ready and set to deliver to you a glorious Hello World message!
> curl http://localhost:8080/
Hello World
TIP: The curl
command is not available by default for Windows users. You can download it from here.
Now, you need to properly create a protected Resource Server.
Set Up an OAuth 2.0 Resource Server
In the Okta dashboard, create an application of type Service it indicates a resource server that does not have a login page or any way to obtain new tokens.
Click Next, type the name of your service, then click Done. You will be presented with a screen similar to the one below. Copy and paste your Client ID and Client Secret for later. They will be useful when you are configuring your application.
Now, let’s code something!
Edit the pom.xml
file and add dependencies for Spring Security and Okta. They will enable all the Spring AND Okta OAuth 2.0 goodness you need:
<!-- security - begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>com.okta.spring</groupId>
<artifactId>okta-spring-boot-starter</artifactId>
<version>0.6.1</version>
</dependency>
<!-- security - end -->
By simply adding this dependency, your code is going to be like a locked house without a key. No one can access your API until you provide a key to your users. Run the command below again.
mvnw spring-boot:run
Now, try to access the Hello World resource:
> curl http://localhost:8080/
{"timestamp":"2018-11-30T01:35:30.038+0000","status":401,"error":"Unauthorized","message":"Unauthorized","path":"/"}
Add Spring Security to Your REST API
Spring Boot has a lot of classpath magic and is able to discover and automatically configure dependencies. Since you have added Spring Security, it automatically secured your resources. Now, you need to configure Spring Security so you can properly authenticate the requests.
NOTE: If you are struggling, you can check the modifications in Git branch
step-1-security-dependencies
.
For that, you need to modify application.properties
as follows (use client_id and client_secret provided by Okta dashboard to your application):
okta.oauth2.issuer=https://{yourOktaDomain}/oauth2/default
okta.oauth2.clientId={clientId}
okta.oauth2.clientSecret={clientSecret}
okta.oauth2.scopes=openid
Spring Boot uses annotations and code for configuring your application so you do not need to edit super boring XML files. This means you can use the Java compiler to validate your configuration!
I usually create configuration in different classes, each one have its own purpose. Create the class net.dovale.okta.secure_rest_api.SecurityConfig
as follows:
package net.dovale.okta.secure_rest_api;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@EnableWebSecurity
@EnableResourceServer
public class SecurityConfig {}
Allow me to explain what the annotations here do:
@EnableWebSecurity
- tells spring we are going to use Spring Security to provide web security mechanisms@EnableResourceServer
- convenient annotation that enables request authentication through OAuth 2.0 tokens. Normally, you would provide aResourceServerConfigurer
bean, but Okta’s Spring Boot starter conveniently provides one for you.
That’s it! Now you have a completely configured and secured Spring REST API without any boilerplate!
Run Spring Boot again and check it with cURL.
mvnw spring-boot:run
# in another shell
curl http://localhost:8080/
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
The message changed, but you still without access… why? Because now the server is waiting for an authorization
header with a valid token. In the next step, you’ll create an access token and use it to access your API.
NOTE: Check the Git branch
step-2-security-configuration
if you have any doubt.
Generate Tokens in Your Spring REST API
So… how do you obtain a token? A resource server has no responsibility to obtain valid credentials: it will only check if the token is valid and proceed with the method execution.
An easy way to achieve a token to generate one using OpenID Connect <debugger/>.
First, you’ll need to create a new Web application in Okta:
Set the Login redirect URIs field to https://oidcdebugger.com/debug
and Grant Type Allowed to Hybrid
. Click Done and copy the client ID for the next step.
Now, on the OpenID Connect
Submit the form to start the authentication process. You’ll receive an Okta login form if you are not logged in or you’ll see the screen below with your custom token.
The token will be valid for one hour so you can do a lot of testing with your API. It’s simple to use the token, just copy it and modify the curl command to use it as follows:
> export TOKEN=${YOUR_TOKEN}
> curl http://localhost:8080 -H "Authorization: Bearer $TOKEN"
Hello World
Add OAuth 2.0 Scopes
OAuth 2.0 scopes is a feature that let users decide if the application will be authorized to make something restricted. For example, you could have “read” and “write” scopes. If an application needs the write scope, it should ask the user this specific scope. These can be automatically handled by Okta’s authorization server.
As a resource server, it can have different endpoints with different scope for each one. Next, you are going to learn how to set different scopes and how to test them.
Add a new annotation to your SecurityConfig
class:
@EnableWebSecurity
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {}
The new @EnableGlobalMethodSecurity(prePostEnabled = true)
annotation tells Spring to use AOP-like method security and prePostEnabled = true
will enable pre and post annotations. Those annotations will enable us to define security programmatically for each endpoint.
Now, make changes to HelloWorldController.java
to create a scope-protected endpoint:
import org.springframework.security.access.prepost.PreAuthorize;
import java.security.Principal;
...
@PreAuthorize("#oauth2.hasScope('profile')")
@GetMapping("/protected/")
public String helloWorldProtected(Principal principal) {
return "Hello VIP " + principal.getName();
}
Pay attention to @PreAuthorize("#oauth2.hasScope('profile')")
. It says: before running this method, verify the request has authorization for the specified Scope. The #oauth2
bit is added by OAuth2SecurityExpressionMethods (check the other methods available) Spring class and is added to your classpath through the spring-cloud-starter-oauth2
dependency.
OK! After a restart, your server will be ready! Make a new request to the endpoint using your current token:
> curl http://localhost:8080/protected/ -H "Authorization: Bearer $TOKEN"
{"error":"access_denied","error_description":"Access is denied"}
Since your token does not have the desired scope, you’ll receive an access is denied
message. To fix this, head back over to OIDC Debugger and add the new scope.
Try again using the newly obtained token:
> curl http://localhost:8080/protected/ -H "Authorization: Bearer $TOKEN"
Hello VIP raphael@dovale.net
That’s it! If you are in doubt of anything, check the latest repository branch finished_sample
.
TIP: Since
profile
is a common OAuth 2.0 scope, you don’t need to change anything in your authorization server. Need to create a custom scope? See this Simple Token Authentication for Java Apps.
Learn More about Spring and REST APIs
In this tutorial, you learned how to use Spring (Boot) to create a resource server and seamlessly integrate it with OAuth 2.0. Both Spring and REST API’s are huge topics, with lots to discuss and learn.
The source code for this tutorial is available on GitHub.
Here are some other posts that will help you further your understanding of both Spring and REST API security:
- What the Heck is OAuth?
- Secure Server-to-Server Communication with Spring Boot and OAuth 2.0
- Spring Boot 2.1: Outstanding OIDC, OAuth 2.0, and Reactive API Support
- Add User Authentication to Your Spring Boot App in 15 Minutes
Like what you learned today? Follow us on Twitter, like us on Facebook, check us out on LinkedIn, and subscribe to our YouTube channel.
Okta Developer Blog Comment Policy
We welcome relevant and respectful comments. Off-topic comments may be removed.