使い方: ソーシャルログインを使用した認証
このガイドでは、認証のためにソーシャルログインプロバイダー (Google、GitHub など) を使用して Spring Authorization Server を構成する方法を説明します。このガイドの目的は、フォームログインを OAuth 2.0 ログインに置き換える方法を説明することです。
Spring Authorization Server は Spring Security に基づいて構築されており、このガイドでは Spring Security の概念を使用します。 |
ソーシャルログインプロバイダーに登録する
開始するには、選択したソーシャルログインプロバイダーでアプリケーションをセットアップする必要があります。一般的なプロバイダーには次のものがあります。
リダイレクト URI の指定を求められるまで、プロバイダーの手順に従います。リダイレクト URI を設定するには、Spring Security と プロバイダーの両方を構成するために使用する registrationId
( google
、my-client
、またはその他の任意の一意の識別子など) を選択します。
registrationId は、Spring Security 内の ClientRegistration の一意の識別子です。デフォルトのリダイレクト URI テンプレートは {baseUrl}/login/oauth2/code/{registrationId} です。詳細については、Spring Security リファレンスのリダイレクト URI の設定を参照してください。 |
例: google の registrationId を使用してポート 9000 でローカルにテストする場合、リダイレクト URI は localhost:9000/login/oauth2/code/google になります。プロバイダーでアプリケーションを設定するときに、この値をリダイレクト URI として入力します。 |
ソーシャルログインプロバイダーによるセットアッププロセスが完了したら、資格情報 (クライアント ID とクライアントシークレット) を取得しているはずです。さらに、プロバイダーのドキュメントを参照し、次の値に注意する必要があります。
認可 URI : プロバイダーで
authorization_code
フローを開始するために使用されるエンドポイント。トークン URI :
authorization_code
をaccess_token
に交換し、オプションでid_token
を交換するために使用されるエンドポイント。JWK セット URI : JWT の署名を検証するためのキーを取得するために使用されるエンドポイント。
id_token
が使用可能な場合に必要です。ユーザー情報 URI : ユーザー情報を取得するために使用されるエンドポイント。
id_token
が使用できない場合に必要となります。ユーザー名の属性 : ユーザーのユーザー名を含む、
id_token
またはユーザー情報レスポンスのいずれかのクレーム。
OAuth 2.0 ログインの構成
ソーシャルログインプロバイダーに登録したら、OAuth 2.0 ログイン用の Spring Security の構成に進むことができます。
クライアントを登録する
次に、前に取得した値を使用して ClientRegistration
を構成します。Okta を例として使用して、次のプロパティを構成します。
okta:
base-url: ${OKTA_BASE_URL}
spring:
security:
oauth2:
client:
registration:
my-client:
provider: okta
client-id: ${OKTA_CLIENT_ID}
client-secret: ${OKTA_CLIENT_SECRET}
scope:
- openid
- profile
- email
provider:
okta:
authorization-uri: ${okta.base-url}/oauth2/v1/authorize
token-uri: ${okta.base-url}/oauth2/v1/token
user-info-uri: ${okta.base-url}/oauth2/v1/userinfo
jwk-set-uri: ${okta.base-url}/oauth2/v1/keys
user-name-attribute: sub
上の例の registrationId は my-client です。 |
上記の例は、環境変数 (OKTA_BASE_URL 、OKTA_CLIENT_ID 、OKTA_CLIENT_SECRET ) を使用してプロバイダー URL、クライアント ID、クライアントシークレットを設定する推奨方法を示しています。詳細については、Spring Boot リファレンスの環境別の設定切り替えを参照してください。 |
この簡単な例は一般的な構成を示していますが、一部のプロバイダーでは追加の構成が必要になります。ClientRegistration
の構成の詳細については、Spring Security リファレンスの Spring Boot プロパティマッピングを参照してください。
認証の構成
最後に、認証にソーシャルログインプロバイダーを使用するように Spring Authorization Server を構成するには、formLogin()
の代わりに oauth2Login()
を使用できます。exceptionHandling()
を AuthenticationEntryPoint
で構成することにより、認証されていないユーザーをプロバイダーに自動的にリダイレクトすることもできます。
前の例に続き、次の例のように @Configuration
を使用して Spring Security を構成します。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean (1)
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
OAuth2AuthorizationServerConfigurer.authorizationServer();
http
.securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.with(authorizationServerConfigurer, (authorizationServer) ->
authorizationServer
.oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0
)
.authorizeHttpRequests((authorize) ->
authorize
.anyRequest().authenticated()
)
// Redirect to the OAuth 2.0 Login endpoint when not authenticated
// from the authorization endpoint
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor( (2)
new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/my-client"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
return http.build();
}
@Bean (3)
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
// OAuth2 Login handles the redirect to the OAuth 2.0 Login endpoint
// from the authorization server filter chain
.oauth2Login(Customizer.withDefaults()); (4)
return http.build();
}
}
1 | プロトコルエンドポイント用の Spring Security フィルターチェーン。 |
2 | OAuth 2.0 ログインエンドポイントにリダイレクトするように AuthenticationEntryPoint を構成します。 |
3 | 認証用の Spring Security フィルターチェーン。 |
4 | OAuth 2.0 ログインを認証用に構成します。 |
開始時に UserDetailsService
を構成した場合は、ここで削除できます。
高度な使用例
デモ認可サーバーのサンプル [GitHub] (英語) では、フェデレーション ID プロバイダーの高度な構成オプションを示します。次の使用例から選択して、それぞれの例を確認してください。
データベース内のユーザーをキャプチャーする
次の例 AuthenticationSuccessHandler
では、カスタムコンポーネントを使用して、ユーザーが最初にログインしたときにローカルデータベースにユーザーをキャプチャーします。
FederatedIdentityAuthenticationSuccessHandler
import java.io.IOException;
import java.util.function.Consumer;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public final class FederatedIdentityAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final AuthenticationSuccessHandler delegate = new SavedRequestAwareAuthenticationSuccessHandler();
private Consumer<OAuth2User> oauth2UserHandler = (user) -> {};
private Consumer<OidcUser> oidcUserHandler = (user) -> this.oauth2UserHandler.accept(user);
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (authentication instanceof OAuth2AuthenticationToken) {
if (authentication.getPrincipal() instanceof OidcUser) {
this.oidcUserHandler.accept((OidcUser) authentication.getPrincipal());
} else if (authentication.getPrincipal() instanceof OAuth2User) {
this.oauth2UserHandler.accept((OAuth2User) authentication.getPrincipal());
}
}
this.delegate.onAuthenticationSuccess(request, response, authentication);
}
public void setOAuth2UserHandler(Consumer<OAuth2User> oauth2UserHandler) {
this.oauth2UserHandler = oauth2UserHandler;
}
public void setOidcUserHandler(Consumer<OidcUser> oidcUserHandler) {
this.oidcUserHandler = oidcUserHandler;
}
}
上記の AuthenticationSuccessHandler
を使用すると、独自の Consumer<OAuth2User>
をプラグインして、フェデレーションアカウントリンクや JIT アカウントプロビジョニングなどの概念のためにデータベースまたはその他のデータストア内のユーザーをキャプチャーできます。ユーザーをメモリ内に単純に保存する例を次に示します。
UserRepositoryOAuth2UserHandler
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.user.OAuth2User;
public final class UserRepositoryOAuth2UserHandler implements Consumer<OAuth2User> {
private final UserRepository userRepository = new UserRepository();
@Override
public void accept(OAuth2User user) {
// Capture user in a local data store on first authentication
if (this.userRepository.findByName(user.getName()) == null) {
System.out.println("Saving first-time user: name=" + user.getName() + ", claims=" + user.getAttributes() + ", authorities=" + user.getAuthorities());
this.userRepository.save(user);
}
}
static class UserRepository {
private final Map<String, OAuth2User> userCache = new ConcurrentHashMap<>();
public OAuth2User findByName(String name) {
return this.userCache.get(name);
}
public void save(OAuth2User oauth2User) {
this.userCache.put(oauth2User.getName(), oauth2User);
}
}
}
クレームを ID トークンにマッピングする
次の例 OAuth2TokenCustomizer
は、認証プロバイダーからのユーザーのクレームを、Spring Authorization Server によって生成された id_token
にマップします。
FederatedIdentityIdTokenCustomizer
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
public final class FederatedIdentityIdTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> {
private static final Set<String> ID_TOKEN_CLAIMS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
IdTokenClaimNames.ISS,
IdTokenClaimNames.SUB,
IdTokenClaimNames.AUD,
IdTokenClaimNames.EXP,
IdTokenClaimNames.IAT,
IdTokenClaimNames.AUTH_TIME,
IdTokenClaimNames.NONCE,
IdTokenClaimNames.ACR,
IdTokenClaimNames.AMR,
IdTokenClaimNames.AZP,
IdTokenClaimNames.AT_HASH,
IdTokenClaimNames.C_HASH
)));
@Override
public void customize(JwtEncodingContext context) {
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) {
Map<String, Object> thirdPartyClaims = extractClaims(context.getPrincipal());
context.getClaims().claims(existingClaims -> {
// Remove conflicting claims set by this authorization server
existingClaims.keySet().forEach(thirdPartyClaims::remove);
// Remove standard id_token claims that could cause problems with clients
ID_TOKEN_CLAIMS.forEach(thirdPartyClaims::remove);
// Add all other claims directly to id_token
existingClaims.putAll(thirdPartyClaims);
});
}
}
private Map<String, Object> extractClaims(Authentication principal) {
Map<String, Object> claims;
if (principal.getPrincipal() instanceof OidcUser) {
OidcUser oidcUser = (OidcUser) principal.getPrincipal();
OidcIdToken idToken = oidcUser.getIdToken();
claims = idToken.getClaims();
} else if (principal.getPrincipal() instanceof OAuth2User) {
OAuth2User oauth2User = (OAuth2User) principal.getPrincipal();
claims = oauth2User.getAttributes();
} else {
claims = Collections.emptyMap();
}
return new HashMap<>(claims);
}
}
次の例のように、@Bean
として発行することで、このカスタマイザーを使用するように Spring Authorization Server を構成できます。
FederatedIdentityIdTokenCustomizer
を構成する @Bean
public OAuth2TokenCustomizer<JwtEncodingContext> idTokenCustomizer() {
return new FederatedIdentityIdTokenCustomizer();
}