はじめに
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 トークンを提供します
演習
- Google で OAuth2 ログインを構成する: Google Cloud Console に OAuth アプリを登録し、ソーシャル ログイン フローを実装します。
- リソースサーバーを作成して認可サーバーから JWT を検証し、Postman/HTTPie でテストします
- ハイブリッド認証の実装: 同じアプリケーションでユーザー名/パスワード ログイン (JWT カスタム) と Google OAuth2 ログインの両方をサポートします。