Secure Spring Boot APIs with OAuth2: A Hands‑On Tutorial
This article walks through building a Spring Boot application that protects HTTP endpoints using OAuth2, covering password and client‑credentials flows, Maven setup, resource and authorization server configuration, in‑memory users, token retrieval, and accessing secured resources with detailed code examples.
OAuth2 Overview
OAuth2 defines four grant types (authorization code, implicit, password, client credentials). For API‑to‑API integration the password and client_credentials grants are most practical because they do not require a user‑agent redirect.
Project Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>Test Endpoints
A simple @RestController exposes a public /product/{id} endpoint and a protected /order/{id} endpoint.
@RestController
public class TestEndpoints {
@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
return "product id : " + id;
}
@GetMapping("/order/{id}")
public String getOrder(@PathVariable String id) {
return "order id : " + id;
}
}Resource Server Configuration
The resource server is enabled with @EnableResourceServer. All requests to /order/** require authentication, while the server is stateless and bound to the resource id order.
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("order").stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().anyRequest()
.and()
.anonymous()
.and()
.authorizeRequests()
.antMatchers("/order/**").authenticated();
}
}Authorization Server Configuration
The authorization server stores tokens in Redis for fast access and automatic expiration. Two in‑memory clients are defined: client_1 – uses client_credentials grant. client_2 – uses password grant.
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client_1").resourceIds("order")
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("select").authorities("client").secret("123456")
.and()
.withClient("client_2").resourceIds("order")
.authorizedGrantTypes("password", "refresh_token")
.scopes("select").authorities("client").secret("123456");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}Spring Security User Configuration
An in‑memory UserDetailsService provides two users for the password grant. The same password and authorities are used for both users.
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
@Override
protected UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());
manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().anyRequest()
.and().authorizeRequests()
.antMatchers("/oauth/*").permitAll();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}OAuth2 Endpoints
Spring Boot automatically creates the following endpoints: /oauth/authorize (POST) /oauth/token (GET/POST)
/oauth/check_token /oauth/errorObtaining an Access Token
Password grant – request a token on behalf of a user:
http://localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=123456Response (JSON):
{
"access_token":"950a7cc9-5a8a-42c9-a693-40e817b1a4b0",
"token_type":"bearer",
"refresh_token":"773a0fcd-6023-45f8-8848-e141296cb3cb",
"expires_in":27036,
"scope":"select"
}Client credentials grant – request a token for a service without a user:
http://localhost:8080/oauth/token?grant_type=client_credentials&scope=select&client_id=client_1&client_secret=123456Response (JSON):
{
"access_token":"56465b41-429d-436c-ad8d-613d476ff322",
"token_type":"bearer",
"expires_in":25074,
"scope":"select"
}Accessing Protected Resources
Calling /order/1 without a token returns an unauthorized error. Supplying the access_token obtained from either grant type allows the request to succeed:
curl "http://localhost:8080/order/1?access_token=950a7cc9-5a8a-42c9-a693-40e817b1a4b0"Response: order id : 1 The public /product/1 endpoint works without authentication.
Debugging Information
When the password grant is used, the authentication object contains the user’s principal and authorities. With the client‑credentials grant the authentication object represents the client itself (authorities defined in the client configuration). This difference is reflected in the debug logs and confirms that the server distinguishes between user‑based and service‑based access.
Key Takeaways
Define resource and authorization servers in a single configuration class for a minimal demo.
Store tokens in Redis to benefit from fast look‑ups and automatic expiration.
Use in‑memory client and user definitions for quick testing; replace with persistent stores (e.g., JDBC) in production.
Protect API endpoints with .antMatchers("/order/**").authenticated() while leaving public endpoints unrestricted.
Both password and client‑credentials grants return a bearer token that can be passed as the access_token query parameter or an Authorization: Bearer header.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
