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.

Programmer DD
Programmer DD
Programmer DD
Secure Spring Boot APIs with OAuth2: A Hands‑On Tutorial

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/error

Obtaining 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=123456

Response (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=123456

Response (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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaRedisSpring BootOAuth2API Securityspring-securityClient CredentialsPassword Grant
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.