Backend Development 13 min read

OAuth 2.0 Tutorial with Spring Boot Implementation and Testing

This article introduces OAuth 2.0 concepts, explains its roles and grant types, and provides a complete Spring Boot implementation—including configuration classes, dependency setup, and test endpoints—followed by step‑by‑step testing results with screenshots.

Top Architect
Top Architect
Top Architect
OAuth 2.0 Tutorial with Spring Boot Implementation and Testing

Hello everyone, I am a senior architect.

1. OAuth 2.0 Overview

OAuth 2.0 (Open Authorization 2.0) is an open standard that allows third‑party applications to access protected resources on behalf of a resource owner without sharing the owner's credentials. It is widely used for secure delegation and authentication on the Internet.

The core concepts and workflow of OAuth 2.0 are:

Roles:

Resource Owner: The user who owns the protected resources.

Client: The third‑party application that wants to access the resource owner's protected resources.

Authorization Server: The server that authenticates the resource owner and issues access tokens.

Resource Server: The server that hosts the protected resources; it may be the same as the authorization server or a different one.

Grant Types:

OAuth 2.0 supports four main grant types:

Authorization Code Grant: Commonly used by third‑party login integrations.

Implicit Grant: Does not require a client server; the token is obtained directly in the browser, suitable for pure static pages.

Password Grant: The user provides username and password directly to the client; suitable only when the client is highly trusted.

Client Credentials Grant: The client obtains a token on its own behalf, useful for service‑to‑service communication.

2. Code Setup

2.1 Authorization Server (port 8080)

Import the following Maven dependencies:

org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-security
2.2.5.RELEASE
org.springframework.cloud
spring-cloud-starter-oauth2
2.2.5.RELEASE

Only two configuration classes are needed:

MyAuthorizationConfig class

@Configuration
@EnableAuthorizationServer
public class MyAuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * Client storage strategy – using in‑memory for demo.
     */
    @Autowired
    private ClientDetailsService clientDetailsService;

    /**
     * Authentication manager needed for password grant.
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("test")
                .secret(new BCryptPasswordEncoder().encode("123456"))
                .resourceIds("order")
                .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token")
                .scopes("all")
                .autoApprove(false)
                .redirectUris("http://www.baidu.com");
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService);
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore());
        services.setAccessTokenValiditySeconds(60 * 60 * 2);
        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 3);
        return services;
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new InMemoryAuthorizationCodeServices();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authorizationCodeServices(authorizationCodeServices())
                 .authenticationManager(authenticationManager)
                 .tokenServices(tokenServices())
                 .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }
}

SecurityConfig class

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /** Password encoder */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginProcessingUrl("/login")
            .permitAll()
            .and()
            .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password(new BCryptPasswordEncoder().encode("123456"))
            .roles("admin");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

2.2 Resource Server (port 8081)

Import the same dependencies as the authorization server and add a configuration class ResourceServerConfig :

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    /** Token validation service (remote call to auth server) */
    @Bean
    public RemoteTokenServices tokenServices() {
        RemoteTokenServices services = new RemoteTokenServices();
        services.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        services.setClientId("test");
        services.setClientSecret("123456");
        return services;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("order")
                 .tokenServices(tokenServices());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/**").access("#oauth2.hasScope('all')")
            .anyRequest().authenticated();
    }
}

Test controller:

@RestController
public class TestController {
    @GetMapping("/test")
    public String hello() {
        return "hello world";
    }
}

3. Test Results

Access the authorization endpoint:

http://localhost:8080/oauth/authorize?client_id=test&response_type=code&scope=all&redirect_uri=http://www.baidu.com

Log in with username admin and password 123456 to obtain an authorization code, then exchange it for an access token (screenshots shown).

When calling the resource server without a token, the request is rejected (screenshot). With a valid token, the protected endpoint returns the expected response (screenshot).

Feel free to discuss, ask questions, or contact the author for further communication.

Additional promotional content about ChatGPT services, community groups, and interview material is included in the original article.

BackendJavaSpring BootsecurityAuthenticationOAuth2
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.