How to Build an OAuth2 Security Framework in Spring Cloud Microservices

This article explains the fundamentals of OAuth2, its four grant types and roles, and provides step‑by‑step Spring Boot and Spring Cloud configurations—including authorization and resource server setup, custom token filters, and Zuul gateway integration—to secure microservice APIs.

Architect's Alchemy Furnace
Architect's Alchemy Furnace
Architect's Alchemy Furnace
How to Build an OAuth2 Security Framework in Spring Cloud Microservices

OAuth2 (Open Authorization) is a standard that enables third‑party applications to obtain limited access to user resources without exposing user credentials; OAuth2.0 is the successor of OAuth1.0 and defines four grant types.

OAuth2 Grant Types

Authorization Code : Two‑step flow that returns an access token and a refresh token; the access token has a limited lifespan and can be refreshed using the refresh token.

Implicit : Simplified flow that obtains the token directly from the authorization server; easier but less secure.

Password : Uses the resource owner's username and password to obtain tokens; suitable for trusted first‑party applications.

Client Credentials : No user involvement; the client authenticates directly with the authorization server, typically used for service‑to‑service communication.

The OAuth2 model involves four roles: third‑party application (client), resource owner (user), authorization server, and resource server.

System Integration Overview

Environment:

JDK 8

Spring Boot 2.1.6.RELEASE

Spring Cloud Greenwich.SR2

Spring Cloud Starter OAuth2

Key points for the authorization server configuration:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private MyUserDetailsService myUserDetailsService;
    @Bean
    public RedisTemplateTokenStore tokenStore() {
        return new RedisTemplateTokenStore(redisTemplate);
    }
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }
    @Primary
    @Bean
    public AuthorizationServerTokenServices defaultTokenServices() {
        CustomTokenServices tokenServices = new CustomTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setClientDetailsService(clientDetails());
        tokenServices.setAccessTokenValiditySeconds(60 * 30);
        tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24);
        return tokenServices;
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                 .userDetailsService(myUserDetailsService)
                 .tokenGranter(tokenGranter())
                 .authenticationManager(authenticationManager);
        endpoints.tokenServices(defaultTokenServices());
    }
}

The resource server is marked with @EnableResourceServer and shares similar configuration:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    private static final String RESOURCE_ID = "oauth_server_service";
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID).stateless(false);
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/oauth/token", "/oauth/check_token", "/oauth/exit", "/oauth/renewal", "/actuator/**").permitAll()
            .anyRequest().authenticated()
            .and().httpBasic();
    }
}

A custom filter PermitAuthenticationFilter removes the Authorization header for whitelisted URLs, allowing unauthenticated access where needed.

@Component
public class PermitAuthenticationFilter extends OncePerRequestFilter {
    @Value("${security.oauth2.resource.permit-all-urls:/actuator,/actuator/**}")
    private String permitAllUrls;
    public static final String H_AUTHORIZATION = "Authorization";
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String[] urlsArray = permitAllUrls.split(",");
        for (String permitUrl : urlsArray) {
            if (permitUrl.equals("/**") || permitUrl.equals(request.getRequestURI())) {
                request = new HttpServletRequestWrapper(request) {
                    @Override
                    public String getHeader(String name) {
                        if (H_AUTHORIZATION.equalsIgnoreCase(name)) {
                            return null;
                        }
                        return super.getHeader(name);
                    }
                };
            }
        }
        filterChain.doFilter(request, response);
    }
}

For Zuul gateway integration, the security configuration disables CSRF, permits a whitelist of endpoints, and ensures the Authorization header is not stripped by setting sensitiveHeaders to empty.

@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String[] AUTH_WHITELIST = {"/configuration/ui","/configuration/security","/doc.html","/actuator/**","/webjars/**","/swagger-ui.html","/**"};
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers(AUTH_WHITELIST).permitAll()
            .anyRequest().authenticated()
            .and().httpBasic();
    }
}

Key Zuul properties:

zuul:
  add-proxy-headers: true
  retryable: true
  sensitiveHeaders: 
  routes:
    basic-workflow: /workflow-service/**
    basic-system: /userinfo-service/**
    ...
security:
  oauth2:
    client:
      access-token-uri: http://localhost:8000/auth-service/oauth/token
      user-authorization-uri: http://localhost:8000/auth-service/oauth/authorize
      client-id: webapp
    resource:
      user-info-uri: http://localhost:8000/auth-service/user
      prefer-token-info: false
JavaMicroservicesOAuth2Spring Security
Architect's Alchemy Furnace
Written by

Architect's Alchemy Furnace

A comprehensive platform that combines Java development and architecture design, guaranteeing 100% original content. We explore the essence and philosophy of architecture and provide professional technical articles for aspiring architects.

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.