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 feedback@compilenrun.com. I’d greatly appreciate your feedback! :)