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