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 部分:應用程式安全

亞洲開發網

簡介

OAuth2 是一種流行的授權標準,允許第三方應用程式在不共享密碼的情況下存取資源。從「使用 Google 登入」到微服務中的服務到服務驗證,OAuth2 可以處理這一切。本文介紹如何在 Spring Boot 4.x 中整合 OAuth2。


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(取代隱含流程)
裝置代碼智慧電視、CLI 工具

1.3 OpenID 連線 (OIDC)

OIDC 是 OAuth2 之上的一層,增加了身分層:

  • OAuth2:「允許此應用程式存取我的 Google 雲端硬碟」(授權)
  • OIDC:「使用Google帳號登入」(驗證+授權)

OIDC 端點:

  • /.well-known/openid-configuration
  • ID Token(包含使用者資訊)
  • 使用者資訊端點

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 Custom + 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 Token

練習

  1. 設定 Google OAuth2 登入:在 Google Cloud Console 上註冊 OAuth 應用,實現社群登入流程
  2. 建立資源伺服器以從授權伺服器驗證 JWT,使用 Postman/HTTPie 進行測試 3.實現混合身份驗證:在同一應用程式中同時支援使用者名稱/密碼登入(JWT自訂)和Google OAuth2登錄