Spring Security OAuth2
Introduction
OAuth 2.0 has become the industry standard protocol for authorization, allowing third-party applications to access resources on behalf of users without exposing their credentials. Spring Security OAuth2 provides comprehensive support for implementing OAuth 2.0 in your Spring applications, both as a client and as an authorization server.
In this tutorial, you'll learn:
- What OAuth 2.0 is and why it's important
- Core concepts and terminology in OAuth 2.0
- How to configure Spring Security OAuth2 client
- How to implement a resource server
- How to secure your APIs using OAuth 2.0
- Best practices for OAuth 2.0 implementation
OAuth 2.0 Fundamentals
What is OAuth 2.0?
OAuth 2.0 is an authorization framework that enables third-party applications to obtain limited access to a user's account on a server. Instead of using the user's credentials to access protected resources, OAuth 2.0 uses tokens.
Key OAuth 2.0 Roles
- Resource Owner: The user who owns the data (typically your application's end user)
- Client: The application requesting access to the resources
- Authorization Server: The server issuing access tokens after authenticating the resource owner
- Resource Server: The server hosting the protected resources
OAuth 2.0 Flow Types
- Authorization Code Flow: Most common and secure flow for server-side applications
- Implicit Flow: Simplified flow for browser-based applications (less secure)
- Resource Owner Password Credentials Flow: Used when there is a high trust between client and resource owner
- Client Credentials Flow: For server-to-server authentication with no user involved
Setting Up Spring Security OAuth2
To get started with Spring Security OAuth2, you need to add the necessary dependencies to your project.
Maven Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
Gradle Dependencies
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
Implementing OAuth2 Client
Spring Security makes it easy to implement an OAuth2 client. Let's configure our application to use OAuth2 for authentication with popular providers like Google, GitHub, or Facebook.
Configuration Properties
Add the following to your application.yml
or application.properties
file:
spring:
security:
oauth2:
client:
registration:
google:
client-id: your-google-client-id
client-secret: your-google-client-secret
scope:
- email
- profile
github:
client-id: your-github-client-id
client-secret: your-github-client-secret
scope:
- user:email
- read:user
Security Configuration Class
Create a configuration class to set up OAuth2 login:
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.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/", "/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.permitAll()
);
return http.build();
}
}
Creating Login Page
Create a simple login page that includes links to authenticate with the configured providers:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Welcome to Our App</h1>
<div>
<h2>Login with:</h2>
<div>
<a href="/oauth2/authorization/google">Google</a>
</div>
<div>
<a href="/oauth2/authorization/github">GitHub</a>
</div>
</div>
</body>
</html>
Accessing User Information
Once a user is authenticated, you can access their information in your controllers:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserController {
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal OAuth2User principal, Model model) {
String name = principal.getAttribute("name");
String email = principal.getAttribute("email");
model.addAttribute("name", name);
model.addAttribute("email", email);
return "dashboard";
}
}
Implementing an OAuth2 Resource Server
A resource server is an application that hosts protected resources and can validate access tokens. Let's implement a simple REST API secured with OAuth2.
JWT Configuration
First, configure your application to validate JWT tokens:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-auth-server.com
Resource Server Configuration
Create a configuration class for 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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.requestMatchers("/api/admin/**").hasAuthority("SCOPE_admin")
.requestMatchers("/api/**").hasAuthority("SCOPE_read")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt()
);
return http.build();
}
}
REST Controller with OAuth2 Protection
Create a REST controller with protected endpoints:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class ResourceController {
@GetMapping("/public")
public Map<String, String> publicEndpoint() {
return Collections.singletonMap("message", "This is a public endpoint");
}
@GetMapping("/user")
public Map<String, Object> userEndpoint(@AuthenticationPrincipal Jwt jwt) {
return Collections.singletonMap("user_data",
Map.of(
"sub", jwt.getSubject(),
"name", jwt.getClaims().get("name"),
"scope", jwt.getClaims().get("scope")
));
}
@GetMapping("/admin")
public Map<String, String> adminEndpoint() {
return Collections.singletonMap("message", "This is an admin endpoint");
}
}
Real-World Application: Social Login with Custom User Details
Let's create a more comprehensive example that handles social login and stores custom user information.
User Entity
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "users")
public class User {
@Id
private String id;
private String name;
private String email;
private String provider; // "google", "github", etc.
private String providerId;
private String imageUrl;
// Getters and setters
}
OAuth2 User Service
Create a custom OAuth2 user service to process user information:
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
public CustomOAuth2UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oauth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getRegistrationId();
String providerId;
String name;
String email;
String imageUrl = null;
if (provider.equals("google")) {
providerId = oauth2User.getAttribute("sub");
name = oauth2User.getAttribute("name");
email = oauth2User.getAttribute("email");
imageUrl = oauth2User.getAttribute("picture");
} else if (provider.equals("github")) {
providerId = oauth2User.getAttribute("id").toString();
name = oauth2User.getAttribute("name");
email = oauth2User.getAttribute("email");
imageUrl = oauth2User.getAttribute("avatar_url");
} else {
// Handle other providers
throw new OAuth2AuthenticationException("Provider not supported: " + provider);
}
// Find existing user or create new one
User user = userRepository.findByProviderAndProviderId(provider, providerId)
.orElseGet(() -> {
User newUser = new User();
newUser.setId(java.util.UUID.randomUUID().toString());
newUser.setProvider(provider);
newUser.setProviderId(providerId);
return newUser;
});
user.setName(name);
user.setEmail(email);
user.setImageUrl(imageUrl);
userRepository.save(user);
return oauth2User;
}
}
Updated Security Configuration
Update the security configuration to use the custom user service:
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.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class OAuth2SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
public OAuth2SecurityConfig(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/", "/public/**", "/error", "/webjars/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.loginPage("/login")
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.defaultSuccessUrl("/dashboard", true)
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.permitAll()
);
return http.build();
}
}
Best Practices for OAuth2 Implementation
-
Always use HTTPS: OAuth2 security depends on the confidentiality of tokens, which can only be guaranteed over HTTPS.
-
Validate all tokens: Verify that tokens are properly signed, not expired, and have the required scopes.
-
Use short-lived access tokens: Set reasonable expiration times for access tokens and implement refresh token rotation.
-
Implement PKCE: For public clients, use Proof Key for Code Exchange (PKCE) to prevent authorization code interception attacks.
-
Be careful with scopes: Only request the minimum scopes necessary for your application to function.
-
Securely store client secrets: Never expose client secrets in client-side code or version control.
-
Implement proper CORS settings: Configure your resource server with appropriate CORS settings.
Common Challenges and Solutions
Challenge 1: CORS Issues
When your frontend and backend are on different domains, you might encounter CORS issues.
Solution:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("https://your-frontend-domain.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
Challenge 2: Token Validation
Ensuring tokens are valid and have the required scopes.
Solution:
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthoritiesClaimName("scope");
grantedAuthoritiesConverter.setAuthorityPrefix("SCOPE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
Summary
In this tutorial, we covered:
- The fundamental concepts of OAuth 2.0 authorization framework
- How to implement an OAuth2 client in Spring Security for social login
- How to secure REST APIs with OAuth2 token validation
- Best practices for implementing OAuth2 in your applications
- Solutions to common challenges in OAuth2 implementations
Spring Security OAuth2 provides a robust framework for implementing secure authentication and authorization in your applications. By following the patterns and practices outlined in this tutorial, you can create secure applications that leverage industry-standard authorization protocols.
Additional Resources
- Spring Security OAuth2 Documentation
- OAuth 2.0 Specification
- Spring Security Reference
- JWT.io - For debugging JWT tokens
Exercises
-
Basic Implementation: Set up social login with Google in a Spring Boot application.
-
Custom User Details: Extend the OAuth2 login to store user information in a database.
-
Resource Server: Create a REST API with endpoints requiring different scopes.
-
Complete Application: Build a full-stack application with an Angular/React frontend that authenticates against your Spring OAuth2 backend.
-
Advanced: Implement an OAuth2 authorization server using Spring Authorization Server project.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)