OAuth 2.1 is an updated version of the OAuth 2.0 authorization framework, providing enhanced security features and clarifications. It addresses some of the limitations and ambiguities present in OAuth 2.0, making it more robust for modern applications. In this guide, we’ll walk through implementing OAuth 2.1 with Spring Security 6, covering client setup, authorization server configuration, and resource server integration.
What is OAuth 2.1?
OAuth 2.1 builds upon OAuth 2.0 by introducing several improvements, such as Proof Key for Code Exchange (PKCE) for public clients, safer handling of authorization codes, and more secure token exchange processes. These enhancements aim to protect against common vulnerabilities like authorization code interception and client impersonation.
What is Spring Security?
Spring Security is a comprehensive security framework for Java applications. It provides robust solutions for authentication and authorization, supporting various protocols including OAuth 2.0 and OAuth 2.1. With Spring Security 6, you can easily integrate OAuth 2.1 into your application, leveraging its powerful features and configurations.
How do I set up an OAuth 2.1 Authorization Server?
Setting up an OAuth 2.1 authorization server involves configuring Spring Security to handle client registrations, authorization grants, and token issuance. Here’s a step-by-step guide:
Step 1: Add Dependencies
First, ensure your pom.xml includes the necessary dependencies for Spring Security OAuth2:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>1.0.0-M2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Step 2: Configure the Authorization Server
Create a configuration class to set up the authorization server:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore());
}
}
Step 3: Register Clients
Define client details in a configuration file or database. For simplicity, we’ll use an in-memory client registry:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import java.util.Arrays;
@Configuration
public class ClientConfig {
@Bean
public ClientDetailsService clientDetailsService() {
return (ClientDetailsService) clients -> {
BaseClientDetails client = new BaseClientDetails();
client.setClientId("client");
client.setClientSecret("{noop}secret");
client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "refresh_token"));
client.setScope(Arrays.asList("read", "write"));
client.setRegisteredRedirectUri(Arrays.asList("http://localhost:8080/callback"));
client.setAccessTokenValiditySeconds(3600);
client.setRefreshTokenValiditySeconds(2592000);
return client;
};
}
}
Step 4: Secure HTTP Endpoints
Configure HTTP security to protect your endpoints:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/oauth/authorize", "/login**", "/error**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
@Bean
@Override
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
How do I implement a Resource Server?
A resource server protects resources and verifies access tokens. Here’s how to set it up:
Step 1: Add Dependencies
Ensure your pom.xml includes the necessary dependencies for the resource server:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Step 2: Configure the Resource Server
Create a configuration class to set up the resource server:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
@Configuration
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/oauth2/jwks").build();
}
}
Step 3: Secure Resources
Protect your API endpoints using Spring Security annotations:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ResourceController {
@GetMapping("/api/resource")
public String getResource() {
return "This is a protected resource.";
}
}
How do I implement PKCE for Public Clients?
Proof Key for Code Exchange (PKCE) is a security extension for OAuth 2.0 authorization code flow. It is crucial for public clients like single-page applications (SPAs) that cannot keep client secrets secure. Here’s how to enable PKCE:
Step 1: Update Client Configuration
Ensure your client is configured to use PKCE:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import java.util.Arrays;
@Configuration
public class ClientConfig {
@Bean
public ClientDetailsService clientDetailsService() {
return (ClientDetailsService) clients -> {
BaseClientDetails client = new BaseClientDetails();
client.setClientId("client");
client.setClientSecret("{noop}secret");
client.setAuthorizedGrantTypes(Arrays.asList("authorization_code", "refresh_token"));
client.setScope(Arrays.asList("read", "write"));
client.setRegisteredRedirectUri(Arrays.asList("http://localhost:8080/callback"));
client.setAccessTokenValiditySeconds(3600);
client.setRefreshTokenValiditySeconds(2592000);
client.setAdditionalInformation(Map.of("require-proof-key", true)); // Enable PKCE
return client;
};
}
}
Step 2: Update Authorization Server Configuration
Ensure your authorization server supports PKCE:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.builders.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new InMemoryAuthorizationCodeServices();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authorizationCodeServices(authorizationCodeServices());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
Security Considerations
Client Secrets
Always keep client secrets secure. Never expose them in client-side code or version control systems. Use environment variables or secure vaults to manage sensitive information.
Token Validation
Validate tokens on the resource server to ensure they are valid and have not been tampered with. This prevents unauthorized access to protected resources.
PKCE Usage
Use PKCE for public clients to mitigate authorization code interception attacks. This is especially important for SPAs and mobile apps that cannot securely store client secrets.
Troubleshooting Common Issues
Invalid Client Secret
If you encounter an “invalid_client” error, double-check that the client secret is correct and properly encoded. Ensure it matches the value stored in your client registry.
Unauthorized Access
If you receive a “401 Unauthorized” error, verify that the token is valid and has the necessary scopes. Check the token’s expiration and audience claims.
Token Endpoint Not Found
If the token endpoint is not found, ensure that your authorization server is correctly configured and running. Verify that the endpoint URL is correct and accessible.
Quick Reference
@EnableAuthorizationServer- Enables OAuth 2.0 authorization server support.@EnableWebSecurity- Enables web security configuration.TokenStore- Stores and manages OAuth 2.0 tokens.JwtDecoder- Decodes and validates JWT tokens.PKCE- Enhances security for public clients by preventing authorization code interception.
Comparison Table
| Approach | Pros | Cons | Use When |
|---|---|---|---|
| In-Memory Token Store | Simple setup | Limited scalability | Development and testing |
| JDBC Token Store | Persistent storage | Requires database setup | Production environments |
Key Takeaways
- Set up an OAuth 2.1 authorization server using Spring Security.
- Implement a resource server to protect your API endpoints.
- Use PKCE for public clients to enhance security.
- Validate tokens to prevent unauthorized access.
🎯 Key Takeaways
- Configure an OAuth 2.1 authorization server with Spring Security.
- Implement a resource server to secure API endpoints.
- Use PKCE for public clients to protect against authorization code interception.
- Validate tokens to ensure secure access to resources.
📋 Quick Reference
@EnableAuthorizationServer- Enables OAuth 2.0 authorization server support.@EnableWebSecurity- Enables web security configuration.TokenStore- Stores and manages OAuth 2.0 tokens.JwtDecoder- Decodes and validates JWT tokens.PKCE- Enhances security for public clients by preventing authorization code interception.
Configure the client
Set up client details in your configuration.Request the token
Initiate the authorization code flow to obtain a token.Validate the response
Check the token validity before accessing resources.v2.0 NEW v1.5 DEPRECATED
- Requirement 1 - completed
- Requirement 2 - completed
- Requirement 3 - pending
🔍 Click to see detailed explanation
Go ahead and implement OAuth 2.1 with Spring Security 6 in your projects. That’s it. Simple, secure, works.

