<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.
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. @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: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.
Spring Boot REST APIs Ultimate Course
Hands-on REST API Development with Spring Boot: Design, Implement, Document, Secure, Test, Consume RESTful APIs
{
"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: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. {
"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. curl -d "grant_type=client_credentials&client_id=client-1&client_secret=password1" localhost:8080/oauth2/token -vThe 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.
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.
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.