Chuyển đến nội dung chính

Lesson 11: OAuth2 & OpenID Connect — Social Login & Resource Server

OAuth2 Authorization Code flow, Client Credentials. Spring Security OAuth2 Client for Google/GitHub login. Resource Server with JWT validation. Spring Authorization Server.

💻 Programming — Lesson 10 Lesson 11: OAuth2 & OpenID Connect — Social Login & Resource Server

Spring Boot 4: From Basics to Advanced

Part 3: Application security

xdev.asia

Introduction

OAuth2 is a popular authorization standard to allow third-party applications to access resources without sharing passwords. From "Login with Google" to service-to-service authentication in microservices, OAuth2 handles it all. This article shows how to integrate OAuth2 in Spring Boot 4.x.


1. OAuth2 Fundamentals

1.1 Roles in OAuth2

┌──────────────────┐
│  Resource Owner   │  ← User (người dùng)
│  (End User)       │
└────────┬─────────┘
         │ Authorize
         ▼
┌──────────────────┐     ┌──────────────────┐
│  Client           │────►│ Authorization    │
│  (Your App)       │◄────│ Server           │
│                   │     │ (Google, GitHub)  │
└────────┬─────────┘     └──────────────────┘
         │ Access Token
         ▼
┌──────────────────┐
│  Resource Server  │  ← API chứa data
│  (API)            │
└──────────────────┘

1.2 OAuth2 Flows

FlowUse Case
Authorization CodeWeb apps, SPAs (with user interaction)
Client CredentialsService-to-service (no user)
PKCEMobile apps, SPAs (replace implicit flow)
Device CodeSmart TV, CLI tools

1.3 OpenID Connect (OIDC)

OIDC is a layer on top of OAuth2, adding identity layer:

  • OAuth2: "Allow this app to access my Google Drive" (Authorization)
  • OIDC: "Sign in with Google account" (Authentication + Authorization)

OIDC endpoints:

  • /.well-known/openid-configuration
  • ID Token (contains user information)
  • UserInfo endpoint

2. OAuth2 Client — Social Login

2.1 Dependencies & Configuration

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
# application.yaml
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: openid, profile, email

          github:
            client-id: ${GITHUB_CLIENT_ID}
            client-secret: ${GITHUB_CLIENT_SECRET}
            scope: read:user, user:email

2.2 Security Config with OAuth2

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/login", "/api/public/**").permitAll()
            .anyRequest().authenticated()
        )
        .oauth2Login(oauth2 -> oauth2
            .loginPage("/login")
            .defaultSuccessUrl("/dashboard")
            .userInfoEndpoint(userInfo -> userInfo
                .userService(customOAuth2UserService)
            )
        );

    return http.build();
}

2.3 Custom OAuth2 User 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 email = oauth2User.getAttribute("email");
        String name = oauth2User.getAttribute("name");

        // Tìm hoặc tạo user trong database
        User user = userRepository.findByEmail(email)
            .orElseGet(() -> {
                User newUser = new User();
                newUser.setEmail(email);
                newUser.setName(name);
                newUser.setProvider(provider);
                newUser.setRoles(Set.of("USER"));
                newUser.setEnabled(true);
                return userRepository.save(newUser);
            });

        return new CustomOAuth2User(oauth2User, user);
    }
}

3. OAuth2 Resource Server — Validate JWT

3.1 Configuration

When your application is a Resource Server (API receives JWT from client):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com
          # hoặc
          jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/public/**").permitAll()
            .anyRequest().authenticated()
        )
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt
                .jwtAuthenticationConverter(jwtAuthenticationConverter())
            )
        );

    return http.build();
}

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthorities =
        new JwtGrantedAuthoritiesConverter();
    grantedAuthorities.setAuthoritiesClaimName("roles");
    grantedAuthorities.setAuthorityPrefix("ROLE_");

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(grantedAuthorities);
    return converter;
}

4. Client Credentials — Service-to-Service

spring:
  security:
    oauth2:
      client:
        registration:
          internal-service:
            provider: custom-auth-server
            client-id: ${SERVICE_CLIENT_ID}
            client-secret: ${SERVICE_CLIENT_SECRET}
            authorization-grant-type: client_credentials
            scope: read, write
        provider:
          custom-auth-server:
            token-uri: https://auth.example.com/oauth2/token
@Service
public class ExternalApiService {

    private final RestClient restClient;

    public ExternalApiService(
            RestClient.Builder builder,
            OAuth2AuthorizedClientManager authorizedClientManager) {

        this.restClient = builder
            .baseUrl("https://api.other-service.com")
            .requestInterceptor(
                new OAuth2ClientHttpRequestInterceptor(authorizedClientManager))
            .build();
    }

    public List<DataResponse> fetchData() {
        return restClient.get()
            .uri("/api/data")
            .retrieve()
            .body(new ParameterizedTypeReference<>() {});
    }
}

5. Combine JWT Custom + OAuth2

In fact, many applications support both:

  • JWT custom: Username/password login
  • OAuth2: Social login (Google, GitHub)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/auth/**", "/oauth2/**").permitAll()
            .anyRequest().authenticated()
        )
        // Custom JWT filter
        .addFilterBefore(jwtAuthFilter,
            UsernamePasswordAuthenticationFilter.class)
        // OAuth2 login (social)
        .oauth2Login(oauth2 -> oauth2
            .successHandler(oAuth2SuccessHandler) // Generate JWT after OAuth2 login
        );

    return http.build();
}

Summary

  • OAuth2 is an authorization standard that allows applications to access resources on behalf of users without needing a password
  • Spring Boot supports OAuth2 Client (social login), Resource Server (validate JWT), and Client Credentials (service-to-service)
  • OpenID Connect adds an identity layer to OAuth2, providing an ID Token containing user information

Exercises

  1. Configure OAuth2 login with Google: register OAuth app on Google Cloud Console, implement social login flow
  2. Create Resource Server to validate JWT from an Authorization Server, test with Postman/HTTPie
  3. Implement hybrid auth: support both username/password login (JWT custom) and Google OAuth2 login in the same application