In this REST API tutorial, I’d like to share with you about implementing authorization server in a Spring Boot application with Spring Security OAuth2 Authorization Server library. In detailed, you’ll learn to create an authorization server that authenticates and issues access tokens (JWT) to REST clients with the following:
To test the authorization server, I’ll show you:
The following key technologies will be used in the demo Spring Boot project (you can use newer versions):
To develop the sample Spring authorization server project, I use Spring Tool Suite IDE (STS) with OpenJDK 23. For API testing, I use curl command-line tool and Postman desktop program. You can use your favorite Java IDE with minimum Java version is 17.
1. Create New Spring Starter Project
2. Enable Spring Authorization Server
3. Create In-memory Client Credentials
4. Understand Get Access Token API
5. Write Code to Test Get Access Token API
6. Decode Issued Access Token
7. Test Get Access Token using curl
8. Test Get Access Token using Postman
Next, let’s start coding a new Spring Boot project for an authorization server which will be used by REST clients to get access tokens.
In Spring Tool Suite, create a new Spring Starter project with the following details:
And for the dependencies, choose the following:
For your reference, below is the main code of the pom.xml file:
<project ...> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.2</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
Note that the spring-boot-starter-test dependency is automatically added by default.
Before writing code to enable Spring authorization server, let’s understand the responsibility of an authorization server in the context of REST API security.
The authorization server will authenticate the clients via the /oauth2/token endpoint. The clients must send the credentials in form of Client ID and Client Secret if the grant type is Client Credentials. If the credentials are valid, then the server will issue a new access token that follows JWT format (JSON Web Token).
To enable Spring authorization server for the application, create a new class named AuthorizationServerConfig with the following initial code:
package net.codejava; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.web.SecurityFilterChain; @Configuration public class AuthorizationServerConfig { @Bean SecurityFilterChain authorizationServerFilterChain(HttpSecurity http) throws Exception { http.with(OAuth2AuthorizationServerConfigurer.authorizationServer(), Customizer.withDefaults()); return http.build(); } }
This Spring configuration class declares a bean of type SecurityFilterChain that activates the authorization server with default security configurations. At this time, the /oauth2/token endpoint is ready to server clients but we need to create some client credentials before testing it. You can also customize the JWTs generated by Spring authorization server, by following this guide.
To tell the server where to get client information, declare a bean of type RegisteredClientRepository in the AuthorizationServerConfig class. For example:
@Bean RegisteredClientRepository registeredClientRepository() { RegisteredClient client1 = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("client-1") .clientName("John Doe") .clientSecret("{noop}password1") .scope("read") .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) .build(); return new InMemoryRegisteredClientRepository(client1); }
Here, we create a RegisteredClient object that represents a client registration with the following details:
And finally we return an InMemoryRegisteredClientRepository object containing the specified client (client1). And the following example shows how to specify multiple clients:
RegisteredClient client1 = ... RegisteredClient client2 = ... RegisteredClient client3 = ... return new InMemoryRegisteredClientRepository(client1, client2, client3);
That’s the code required to enable Spring authorization server with default security settings and in-memory client credentials.
As per the OAuth2 specification, the authorization server should provide the Get Access Token API with:
REST clients must send a request with these details in order to obtain a new access token issued from the authorization server.
If the client credentials are invalid, the server will return HTTP response status 401 (Unauthorized) with the following JSON in the body:
{ "error": "invalid_client" }
Else if the client credentials are valid, the server will return HTTP status 200 (Successful) with the following response body:
{ "access_token": "Encoded Access Token", "token_type": "Bearer", "expires_in": 299 }
This JSON document has three fields:
Based on this description, we’ll write code as well as using tools like curl and Postman to test the authorization server.
Next, let’s write a couple of “unit tests” to verify the authorization server working or not. Firstly, we test the failure case in which incorrect client Id and client Secret are provided. In the test source package, create a new class named SecurityTests with the following code for the first test method:
package net.codejava; import static org.hamcrest.CoreMatchers.is; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; @SpringBootTest @AutoConfigureMockMvc public class SecurityTests { private static final String GET_ACCESS_TOKEN_ENDPOINT = "/oauth2/token"; @Autowired MockMvc mockMvc; @Test public void testGetAccessTokenFail() throws Exception { mockMvc.perform(post(GET_ACCESS_TOKEN_ENDPOINT) .param("client_id", "client-1") .param("client_secret", "test") .param("grant_type", "client_credentials") ) .andExpect(status().isUnauthorized()) .andExpect(jsonPath("$.error", is("invalid_client"))) .andDo(print()); } }
You can see that this test class is annotated with the @SpringBootTest annotation, which means the tests will load the whole Spring application when running. So it’s more likely integration test rather than unit test.
In this test method, we send correct client Id (client-1) but invalid secret (test), and we expect the server return status 401 Unauthorized with a JSON in the response body having the error field containing the value invalid_client (as per description of the Get Access Token API mentioned above).
Secondly, we code the second test method for the successful case, as follows:
@Test public void testGetAccessTokenSuccess() throws Exception { mockMvc.perform(post(GET_ACCESS_TOKEN_ENDPOINT) .param("client_id", "client-1") .param("client_secret", "password1") .param("grant_type", "client_credentials") ) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isString()) .andExpect(jsonPath("$.expires_in").isNumber()) .andExpect(jsonPath("$.token_type", is("Bearer"))) .andDo(print()); }
In this test, we send correct client Id and secret and expect the response as described in the Get Access Token API description.
Run both the methods as JUnit test and you should see they pass the test.
When you run the testGetAccessTokenSuccess() method, the server should return the following response body:
{ "access_token": "eyJraWQiOiJjNT...YCz1r70F0a9B8bb_D_kHyVzjzm8nmzi5ng_Rs13HHZE4xk3SMiitrA", "token_type": "Bearer", "expires_in": 299 }
The Spring authorization server issues a new access token contained in the access_token field. The access token is encoded using Base64 URL encoding so you can use and Base64 decoder to view its content.
Here I use this online JWT decoder to decode the access token. Copy the value of the access_token field into the Encoded textbox and you will get it decoded instantly, as shown below:
The encoded access token is decoded as shown on the right side. You can see the information in the header (key identifier and algorithm name) and payload (subject, audience, issuer, expiration, etc.).
It’s recommended to decode the issued access tokens to verify that they contain the desired information and do not expose security risks.
Now, it’s time to start the Spring application SpringAuthorizationServerExample to test out the Get Access Token API exposed by the authorization server. In command prompt or terminal, you can use the following curl command to test:
curl -d "grant_type=client_credentials&client_id=client-1&client_secret=password1" localhost:8080/oauth2/token -v
The following screenshot shows an example request and response:
Check this guide to learn more about testing REST APIs using curl command line tool. To prettify the JSON in the response, follow this tip.
You can also use a visual tool like Postman to test the authorization server for more convenience. In Postman, create a new request with the following details:
Click the Send button, you should see the output as shown below:
This means the authorization server accepts the request, verifies the client ID and secret, and issue a new access token in the response body. Check this guide to learn more about testing REST APIs using Postman.
The Spring Security OAuth2 Authorization Server library makes it easy to implement a standard, functional authorization server that follows the OAuth2 specification, as I have demonstrated in this tutorial. Based on the code examples illustrated above, I hope you will be able to apply in your own REST API applications that are built using Java, Spring framework, Spring Boot and Spring Security.
For reference, you can download the sample project attached below. To watch the coding in action, I recommend you watch the following video: