Skip to main content

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

  1. Authorization Code Flow: Most common and secure flow for server-side applications
  2. Implicit Flow: Simplified flow for browser-based applications (less secure)
  3. Resource Owner Password Credentials Flow: Used when there is a high trust between client and resource owner
  4. 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

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

groovy
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:

yaml
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:

java
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:

html
<!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:

java
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:

yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://your-auth-server.com

Resource Server Configuration

Create a configuration class for the resource server:

java
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:

java
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

java
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:

java
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:

java
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

  1. Always use HTTPS: OAuth2 security depends on the confidentiality of tokens, which can only be guaranteed over HTTPS.

  2. Validate all tokens: Verify that tokens are properly signed, not expired, and have the required scopes.

  3. Use short-lived access tokens: Set reasonable expiration times for access tokens and implement refresh token rotation.

  4. Implement PKCE: For public clients, use Proof Key for Code Exchange (PKCE) to prevent authorization code interception attacks.

  5. Be careful with scopes: Only request the minimum scopes necessary for your application to function.

  6. Securely store client secrets: Never expose client secrets in client-side code or version control.

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

java
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:

java
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

Exercises

  1. Basic Implementation: Set up social login with Google in a Spring Boot application.

  2. Custom User Details: Extend the OAuth2 login to store user information in a database.

  3. Resource Server: Create a REST API with endpoints requiring different scopes.

  4. Complete Application: Build a full-stack application with an Angular/React frontend that authenticates against your Spring OAuth2 backend.

  5. 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! :)