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

レッスン 11: OAuth2 と OpenID Connect — ソーシャル ログインとリソース サーバー

OAuth2 認証コード フロー、クライアント資格情報。 Google/GitHub ログイン用の Spring Security OAuth2 クライアント。 JWT 検証を備えたリソース サーバー。 Spring認可サーバー。

💻 プログラミング — レッスン 10 レッスン 11: OAuth2 と OpenID Connect — ソーシャル ログインとリソースサーバー

Spring Boot 4: 基本から上級まで

パート 3: アプリケーションのセキュリティ

xdev.asia

はじめに

OAuth2 は、サードパーティのアプリケーションがパスワードを共有せずにリソースにアクセスできるようにするための一般的な認証標準です。 「Google でログイン」からマイクロサービスのサービス間認証まで、OAuth2 がすべてを処理します。この記事では、OAuth2 を Spring Boot 4.x に統合する方法を説明します。


1. OAuth2 の基礎

1.1 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 フロー

フロー使用例
認証コードWeb アプリ、SPA (ユーザー インタラクションあり)
クライアントの資格情報サービス間 (ユーザーなし)
PKCEモバイル アプリ、SPA (暗黙的なフローを置き換える)
デバイスコードスマート TV、CLI ツール

1.3 OpenID Connect (OIDC)

OIDC は、OAuth2 の上に identity レイヤーを追加したレイヤーです。

  • OAuth2: 「このアプリに Google ドライブへのアクセスを許可します」 (承認)
  • OIDC: 「Google アカウントでサインイン」 (認証 + 認可)

OIDC エンドポイント:

  • /.well-known/openid-configuration
  • IDトークン(ユーザー情報を含む)
  • ユーザー情報エンドポイント

2. OAuth2 クライアント — ソーシャル ログイン

2.1 依存関係と構成

<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 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 カスタム OAuth2 ユーザー サービス

@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 リソース サーバー — JWT を検証する

3.1 構成

アプリケーションがリソース サーバーの場合 (API はクライアントから JWT を受け取ります):

<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. クライアント資格情報 - サービス間

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. JWT カスタム + OAuth2 を結合する

実際、多くのアプリケーションは次の両方をサポートしています。

  • JWT カスタム: ユーザー名/パスワードによるログイン
  • OAuth2: ソーシャル ログイン (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();
}

概要

  • OAuth2 は、アプリケーションがパスワードを必要とせずにユーザーに代わってリソースにアクセスできるようにする認証標準です。
  • Spring Boot は、OAuth2 クライアント (ソーシャル ログイン)、リソース サーバー (JWT の検証)、およびクライアント資格情報 (サービス間) をサポートします。
  • OpenID Connect は OAuth2 に ID レイヤーを追加し、ユーザー情報を含む ID トークンを提供します

演習

  1. Google で OAuth2 ログインを構成する: Google Cloud Console に OAuth アプリを登録し、ソーシャル ログイン フローを実装します。
  2. リソースサーバーを作成して認可サーバーから JWT を検証し、Postman/HTTPie でテストします
  3. ハイブリッド認証の実装: 同じアプリケーションでユーザー名/パスワード ログイン (JWT カスタム) と Google OAuth2 ログインの両方をサポートします。