How to Transform a Monolith into a Spring Cloud OAuth2 Resource Server

This tutorial walks through converting a monolithic Spring application into a Spring Cloud microservice by implementing a JWT‑based OAuth2 resource server, covering required dependencies, custom JWT decoding, key separation, security filter configuration, and token parsing customization.

Programmer DD
Programmer DD
Programmer DD
How to Transform a Monolith into a Spring Cloud OAuth2 Resource Server

In the previous article we introduced the concept of a resource server; now we demonstrate converting a monolithic application into a Spring Cloud microservice with a dedicated OAuth2 resource server.

Resource Server Refactoring

Using the Spring Security demo as an example, the original monolith handled authentication and authorization together. After refactoring, authentication is separated, and the service retains only JWT‑based access control. The following steps implement this capability.

Required Dependencies

On top of Spring Security we add dependencies for OAuth2 Resource Server and JWT support:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 资源服务器 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<!-- jose -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
Spring Security 5.x removed the OAuth2.0 authorization server but kept the OAuth2.0 resource server.

JWT Decoding

Validating JWT requires a decoder. Spring Security OAuth2 Resource Server provides a default decoder that reads configuration from spring.security.oauth2.resourceserver properties, typically using the well‑known endpoint to obtain the JWK set, algorithm, issuer, and public‑key location. jwkSetUri – well‑known endpoint for JWK configuration used to validate JWT tokens. jwsAlgorithm – specifies the JWT algorithm, default RSA‑256 . issuerUri – endpoint to fetch OAuth2.0 authorization server metadata. publicKeyLocation – path to the public key; a resource server should only hold the public key, not the private key.

To achieve a smooth transition, the default configuration must be customized by implementing a custom JWT decoder.

Separate Public/Private Keys

The resource server should store only the public key. Export a public key from the existing JKS file:

keytool export -alias felordcn -keystore <jks_path> -file <public.cer_path>

Place the exported .cer file alongside the original JKS and ensure the resource server does not retain the JKS.

Custom JWT Decoder

Using spring-security-oauth2-jose we implement a Nimbus‑based decoder that loads the public key from the classpath and adds custom validation strategies:

@SneakyThrows
@Bean
public JwtDecoder jwtDecoder(@Qualifier("delegatingTokenValidator") DelegatingOAuth2TokenValidator<Jwt> validator) {
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    ClassPathResource resource = new ClassPathResource(this.jwtProperties.getCertInfo().getPublicKeyLocation());
    Certificate certificate = certificateFactory.generateCertificate(resource.getInputStream());
    PublicKey publicKey = certificate.getPublicKey();
    NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withPublicKey((RSAPublicKey) publicKey).build();
    nimbusJwtDecoder.setJwtValidator(validator);
    return nimbusJwtDecoder;
}

Custom Resource Server Configuration

Configure the resource server with a security filter chain that authorizes all requests via a SpEL checker and sets up JWT handling:

@Bean
SecurityFilterChain jwtSecurityFilterChain(HttpSecurity http) throws Exception {
    return http.authorizeRequests(request -> request.anyRequest()
            .access("@checker.check(authentication,request)"))
        .exceptionHandling()
        .accessDeniedHandler(new SimpleAccessDeniedHandler())
        .authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
        .and()
        .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
        .build();
}

After this configuration, protected APIs are secured by Bearer tokens, and the authentication token type becomes JwtAuthenticationToken instead of UsernamePasswordAuthenticationToken.

JWT Custom Parsing

Customize JwtAuthenticationConverter to change the authority prefix and principal claim name, for example removing the default SCOPE_ prefix and using ROLE_ or no prefix:

@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    // Remove default prefix
    jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
    // Set principal claim name (default is sub)
    jwtAuthenticationConverter.setPrincipalClaimName(JwtClaimNames.SUB);
    return jwtAuthenticationConverter;
}

Additional Notes

For testing the resource server, a simple token‑issuing method can simulate an authorization server:

@SneakyThrows
@Test
public void imitateAuthServer() {
    JwtEncoder jwsEncoder = new NimbusJwsEncoder(jwkSource());
    JwtTokenGenerator jwtTokenGenerator = new JwtTokenGenerator(jwsEncoder);
    OAuth2AccessTokenResponse oAuth2AccessTokenResponse = jwtTokenGenerator.tokenResponse();
    System.out.println("oAuth2AccessTokenResponse = " + oAuth2AccessTokenResponse.getAccessToken().getTokenValue());
}

private JWKSource<SecurityContext> jwkSource() {
    ClassPathResource resource = new ClassPathResource("felordcn.jks");
    KeyStore jks = KeyStore.getInstance("jks");
    char[] password = "123456".toCharArray();
    jks.load(resource.getInputStream(), password);
    RSAKey rsaKey = RSAKey.load(jks, "felordcn", password);
    JWKSet jwkSet = new JWKSet(rsaKey);
    return new ImmutableJWKSet<>(jwkSet);
}

With these steps the resource server is fully functional and ready to protect APIs using JWT Bearer tokens.

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.

Backend DevelopmentSpring CloudJWTOAuth2spring-securityResource Server
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.