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