SAML 2.0 ログインの概要
まず、Spring Security 内で SAML 2.0 証明書利用者認証がどのように機能するかを調べます。まず、OAuth 2.0 ログインと同様に、Spring Security は認証を実行するためにユーザーをサードパーティに誘導することがわかります。これは、一連のリダイレクトを通じて行われます。

上の図は、 |
最初に、ユーザーは、認可されていない /private リソースに対して認証されていないリクエストを行います。
Spring Security の AuthorizationFilter は、認証されていないリクエストが AccessDeniedException をスローすることによって拒否されたことを示します。
ユーザーに認可がないため、ExceptionTranslationFilter は認証開始を開始します。構成された AuthenticationEntryPoint は LoginUrlAuthenticationEntryPoint (Javadoc) のインスタンスであり、<saml2:AuthnRequest> 生成エンドポイント Saml2WebSsoAuthenticationRequestFilter にリダイレクトされます。または、複数のアサートパーティを設定した場合は、最初にピッカーページにリダイレクトされます。
次に、Saml2WebSsoAuthenticationRequestFilter は、構成された Saml2AuthenticationRequestFactory を使用して <saml2:AuthnRequest> を作成、署名、シリアライズ、エンコードします。
次に、ブラウザーはこの <saml2:AuthnRequest> を取得し、アサート側に提示します。アサート側はユーザーの認証を試みます。成功すると、<saml2:Response> がブラウザーに返されます。
次に、ブラウザーは <saml2:Response> をアサーションコンシューマーサービスエンドポイントに POST します。
次のイメージは、Spring Security が <saml2:Response> を認証する方法を示しています。

<saml2:Response> の認証 この図は、 |
ブラウザーが <saml2:Response> をアプリケーションに送信すると、ブラウザーは Saml2WebSsoAuthenticationFilter に委譲します。このフィルターは、構成された AuthenticationConverter を呼び出して、HttpServletRequest からレスポンスを抽出することによって Saml2AuthenticationToken を作成します。このコンバーターはさらに RelyingPartyRegistration を解決し、Saml2AuthenticationToken に供給します。
次に、フィルターはトークンを構成済みの AuthenticationManager に渡します。デフォルトでは、OpenSaml4AuthenticationProvider を使用します。
認証が失敗した場合、Failure。
SecurityContextHolderはクリアされます。AuthenticationEntryPointが呼び出され、認証プロセスが再開されます。
認証が成功した場合は、Success .
AuthenticationはSecurityContextHolderに設定されます。Saml2WebSsoAuthenticationFilterはFilterChain#doFilter(request,response)を呼び出して、残りのアプリケーションロジックを続行します。
最小限の依存関係
SAML 2.0 サービスプロバイダーのサポートは spring-security-saml2-service-provider にあります。これは OpenSAML ライブラリから構築されているため、ビルド構成に Shibboleth Maven リポジトリも含める必要があります。別のリポジトリが必要な理由の詳細については、このリンク (英語) を確認してください。
Maven
<repositories>
<!-- ... -->
<repository>
<id>shibboleth-releases</id>
<name>Shibboleth Releases Repository</name>
<url>https://build.shibboleth.net/maven/releases/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-saml2-service-provider</artifactId>
</dependency>
repositories {
// ...
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
}
dependencies {
// ...
implementation 'org.springframework.security:spring-security-saml2-service-provider'
}
最小構成
Spring Boot を使用する場合、アプリケーションをサービスプロバイダーとして構成するには、次の 2 つの基本的な手順を実行します。必要な依存関係を含めます。必要なアサートパーティのメタデータを示します。
| また、この構成では、証明書利用者がすでに証明書利用者に登録されていることを前提としています。 |
ID プロバイダーのメタデータの指定
Spring Boot アプリケーションで、ID プロバイダーのメタデータを指定するには、次のような構成を作成します。
spring:
security:
saml2:
relyingparty:
registration:
adfs:
assertingparty:
entity-id: https://idp.example.com/issuer
verification.credentials:
- certificate-location: "classpath:idp.crt"
singlesignon.url: https://idp.example.com/issuer/sso
singlesignon.sign-request: false内容:
idp.example.com/issuer (英語)は、ID プロバイダーが発行する SAML レスポンスのIssuer属性に含まれる値です。classpath:idp.crtは、SAML レスポンスを検証するための ID プロバイダーの証明書のクラスパス上の場所です。idp.example.com/issuer/sso (英語)は、ID プロバイダーがAuthnRequestインスタンスを予期しているエンドポイントです。adfsは、選択した任意の識別子です。
以上です!
ID プロバイダーとアサーティングパーティはシノニムであり、サービスプロバイダと依存パーティも同義です。これらは、それぞれ AP および RP と略されることがよくあります。 |
ランタイムの期待
前に構成したように、アプリケーションは SAMLResponse パラメーターを含むすべての POST /login/saml2/sso/{registrationId} リクエストを処理します。
POST /login/saml2/sso/adfs HTTP/1.1
SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZ... アサーティングパーティに SAMLResponse を生成させる方法は 2 つあります。
アサーティングパーティに移動できます。登録されている証明書利用者ごとに、クリックして
SAMLResponseを送信できる何らかのリンクまたはボタンが含まれている可能性があります。アプリケーションの保護されたページ(たとえば、
localhost:8080)に移動できます。次に、アプリケーションは構成済みのアサートパーティにリダイレクトし、アサートパーティはSAMLResponseを送信します。
ここから、次へのジャンプを検討してください。
SAML 2.0 ログインが OpenSAML と統合する方法
Spring Security の SAML 2.0 サポートには、いくつかの設計ゴールがあります。
SAML 2.0 操作およびドメインオブジェクトについてはライブラリに依存します。これを実現するために、Spring Security は OpenSAML を使用します。
Spring Security の SAML サポートを使用する場合は、このライブラリが不要であることを確認してください。これを実現するために、Spring Security が契約で OpenSAML を使用するインターフェースまたはクラスはカプセル化されたままになります。これにより、OpenSAML を他のライブラリまたはサポートされていないバージョンの OpenSAML に切り替えることができます。
これら 2 つのゴールの当然の結果として、Spring Security の SAML API は他のモジュールに比べて非常に小さいです。代わりに、OpenSamlXAuthenticationRequestFactory や OpenSamlXAuthenticationProvider などのクラスは、認証プロセスのさまざまなステップをカスタマイズする Converter 実装を公開します。
例: アプリケーションが SAMLResponse を受信し、Saml2WebSsoAuthenticationFilter に委譲すると、フィルターは OpenSamlXAuthenticationProvider に委譲します。
Response の認証 
この図は、Saml2WebSsoAuthenticationFilter 図に基づいています。
![]()
Saml2WebSsoAuthenticationFilter は Saml2AuthenticationToken を作成し、AuthenticationManager を呼び出します。
![]()
AuthenticationManager は、OpenSAML 認証プロバイダーを呼び出します。
認証プロバイダーは、レスポンスを OpenSAML Response に逆直列化し、その署名をチェックします。署名が無効な場合、認証は失敗します。
次に、プロバイダーは EncryptedAssertion 要素を復号化します。復号化が失敗すると、認証は失敗します。
次に、プロバイダーはレスポンスの Issuer 値と Destination 値を検証します。それらが RelyingPartyRegistration にあるものと一致しない場合、認証は失敗します。
その後、プロバイダーは各 Assertion の署名を検証します。いずれかの署名が無効な場合、認証は失敗します。また、レスポンスにもアサーションにも署名がない場合、認証は失敗します。レスポンスまたはすべてのアサーションに署名が必要です。
次に、プロバイダーは、EncryptedID または EncryptedAttribute 要素を復号化します。]。復号化が失敗すると、認証は失敗します。
次に、プロバイダは各アサーションの ExpiresAt および NotBefore タイムスタンプ、<Subject> およびすべての <AudienceRestriction> 条件を検証します。いずれかの検証が失敗すると、認証は失敗します。
その後、プロバイダーは最初のアサーションの AttributeStatement を取得し、それを Map<String, List<Object>> にマップします。また、ROLE_USER 付与権限も付与します。
そして最後に、最初のアサーションから NameID、属性の Map、GrantedAuthority を取得し、Saml2AuthenticatedPrincipal を構築します。次に、そのプリンシパルと権限を Saml2Authentication に配置します。
結果の Authentication#getPrincipal は Spring Security Saml2AuthenticatedPrincipal オブジェクトであり、Authentication#getName は最初のアサーションの NameID 要素にマップされます。Saml2AuthenticatedPrincipal#getRelyingPartyRegistrationId は、関連付けられた RelyingPartyRegistration の識別子を保持します。
OpenSAML 構成のカスタマイズ
Spring Security と OpenSAML の両方を使用するクラスは、クラスの開始時に OpenSamlInitializationService を静的に初期化する必要があります。
Java
Kotlin
static {
OpenSamlInitializationService.initialize();
}
companion object {
init {
OpenSamlInitializationService.initialize()
}
}
これは OpenSAML の InitializationService#initialize に代わるものです。
OpenSAML が SAML オブジェクトを構築、マーシャリング、アンマーシャリングする方法をカスタマイズすることが役立つ場合があります。このような状況では、代わりに OpenSAML の XMLObjectProviderFactory へのアクセスを提供する OpenSamlInitializationService#requireInitialize(Consumer) を呼び出すことができます。
例: 署名されていない AuthNRequest を送信する場合、再認証を強制することができます。その場合、次のように独自の AuthnRequestMarshaller を登録できます。
Java
Kotlin
static {
OpenSamlInitializationService.requireInitialize(factory -> {
AuthnRequestMarshaller marshaller = new AuthnRequestMarshaller() {
@Override
public Element marshall(XMLObject object, Element element) throws MarshallingException {
configureAuthnRequest((AuthnRequest) object);
return super.marshall(object, element);
}
public Element marshall(XMLObject object, Document document) throws MarshallingException {
configureAuthnRequest((AuthnRequest) object);
return super.marshall(object, document);
}
private void configureAuthnRequest(AuthnRequest authnRequest) {
authnRequest.setForceAuthn(true);
}
}
factory.getMarshallerFactory().registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller);
});
}
companion object {
init {
OpenSamlInitializationService.requireInitialize {
val marshaller = object : AuthnRequestMarshaller() {
override fun marshall(xmlObject: XMLObject, element: Element): Element {
configureAuthnRequest(xmlObject as AuthnRequest)
return super.marshall(xmlObject, element)
}
override fun marshall(xmlObject: XMLObject, document: Document): Element {
configureAuthnRequest(xmlObject as AuthnRequest)
return super.marshall(xmlObject, document)
}
private fun configureAuthnRequest(authnRequest: AuthnRequest) {
authnRequest.isForceAuthn = true
}
}
it.marshallerFactory.registerMarshaller(AuthnRequest.DEFAULT_ELEMENT_NAME, marshaller)
}
}
}
requireInitialize メソッドは、アプリケーションインスタンスごとに 1 回だけ呼び出すことができます。
Boot 自動構成のオーバーライドまたは置換
Spring Boot は、証明書利用者用に 2 つの @Bean オブジェクトを生成します。
1 つ目は、アプリケーションを証明書利用者として構成する SecurityFilterChain です。spring-security-saml2-service-provider を含めると、SecurityFilterChain は次のようになります。
Java
Kotlin
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.saml2Login(withDefaults());
return http.build();
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login { }
}
return http.build()
}
アプリケーションが SecurityFilterChain Bean を公開しない場合、Spring Boot は前のデフォルトのものを公開します。
これを置き換えるには、アプリケーション内で Bean を公開します。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/messages/**").hasAuthority("ROLE_USER")
.anyRequest().authenticated()
)
.saml2Login(withDefaults());
return http.build();
}
}
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/messages/**", hasAuthority("ROLE_USER"))
authorize(anyRequest, authenticated)
}
saml2Login {
}
}
return http.build()
}
}
上記の例では、/messages/ で始まる URL に対して USER のロールが必要です。
2 番目の @Bean Spring Boot は RelyingPartyRegistrationRepository (Javadoc) であり、これはアサート側と証明書側のメタデータを表します。これには、アサート側に認証をリクエストするときに証明書利用者が使用する必要がある SSO エンドポイントの場所などが含まれます。
独自の RelyingPartyRegistrationRepository Bean を公開することにより、デフォルトをオーバーライドできます。例: メタデータエンドポイントにアクセスすることで、アサート側の構成を検索できます。
Java
Kotlin
@Value("${metadata.location}")
String assertingPartyMetadataLocation;
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId("example")
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
@Value("\${metadata.location}")
var assertingPartyMetadataLocation: String? = null
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
val registration = RelyingPartyRegistrations
.fromMetadataLocation(assertingPartyMetadataLocation)
.registrationId("example")
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
registrationId は、登録を区別するために選択する任意の値です。 |
または、各詳細を手動で提供することもできます。
Java
Kotlin
@Value("${verification.key}")
File verificationKey;
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
X509Certificate certificate = X509Support.decodeCertificate(this.verificationKey);
Saml2X509Credential credential = Saml2X509Credential.verification(certificate);
RelyingPartyRegistration registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyMetadata(party -> party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
.wantAuthnRequestsSigned(false)
.verificationX509Credentials(c -> c.add(credential))
)
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
@Value("\${verification.key}")
var verificationKey: File? = null
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository {
val certificate: X509Certificate? = X509Support.decodeCertificate(verificationKey!!)
val credential: Saml2X509Credential = Saml2X509Credential.verification(certificate)
val registration = RelyingPartyRegistration
.withRegistrationId("example")
.assertingPartyMetadata { party: AssertingPartyMetadata.Builder ->
party
.entityId("https://idp.example.com/issuer")
.singleSignOnServiceLocation("https://idp.example.com/SSO.saml2")
.wantAuthnRequestsSigned(false)
.verificationX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(
credential
)
}
}
.build()
return InMemoryRelyingPartyRegistrationRepository(registration)
}
|
または、DSL を使用してリポジトリを直接接続することもできます。これにより、自動構成された SecurityFilterChain もオーバーライドされます。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/messages/**").hasAuthority("ROLE_USER")
.anyRequest().authenticated()
)
.saml2Login(saml2 -> saml2
.relyingPartyRegistrationRepository(relyingPartyRegistrations())
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeRequests {
authorize("/messages/**", hasAuthority("ROLE_USER"))
authorize(anyRequest, authenticated)
}
saml2Login {
relyingPartyRegistrationRepository = relyingPartyRegistrations()
}
}
return http.build()
}
}
|
メタデータを定期的にリフレッシュしたい場合は、次のようにリポジトリを CachingRelyingPartyRegistrationRepository でラップします。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class MyCustomSecurityConfiguration {
@Bean
public RelyingPartyRegistrationRepository registrations(CacheManager cacheManager) {
Supplier<IterableRelyingPartyRegistrationRepository> delegate = () ->
new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations
.fromMetadataLocation("https://idp.example.org/ap/metadata")
.registrationId("ap").build());
CachingRelyingPartyRegistrationRepository registrations =
new CachingRelyingPartyRegistrationRepository(delegate);
registrations.setCache(cacheManager.getCache("my-cache-name"));
return registrations;
}
}
@Configuration
@EnableWebSecurity
class MyCustomSecurityConfiguration {
@Bean
fun registrations(cacheManager: CacheManager): RelyingPartyRegistrationRepository {
val delegate = Supplier<IterableRelyingPartyRegistrationRepository> {
InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistrations
.fromMetadataLocation("https://idp.example.org/ap/metadata")
.registrationId("ap").build())
}
val registrations = CachingRelyingPartyRegistrationRepository(delegate)
registrations.setCache(cacheManager.getCache("my-cache-name"))
return registrations
}
}
このようにして、RelyingPartyRegistration のセットはキャッシュの削除スケジュールに基づいてリフレッシュされます。
RelyingPartyRegistration
RelyingPartyRegistration (Javadoc) インスタンスは、依存側とアサート側のメタデータ間のリンクを表します。
RelyingPartyRegistration では、Issuer 値のような依存パーティメタデータを提供できます。このメタデータは、SAML レスポンスが送信されることを期待し、ペイロードに署名または復号化するために所有する資格情報を提供します。
また、AuthnRequests の送信先となる Issuer 値などのアサーティングパーティのメタデータと、依存パーティがペイロードを検証または暗号化する目的で所有するパブリック資格情報を提供できます。
次の RelyingPartyRegistration は、ほとんどのセットアップに最低限必要なものです。
Java
Kotlin
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("my-id")
.build();
val relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("my-id")
.build()
任意の InputStream ソースから RelyingPartyRegistration を作成することもできることに注意してください。そのような例の 1 つは、メタデータがデータベースに保存されている場合です。
String xml = fromDatabase();
try (InputStream source = new ByteArrayInputStream(xml.getBytes())) {
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistrations
.fromMetadata(source)
.registrationId("my-id")
.build();
}より洗練されたセットアップも可能です。
Java
Kotlin
RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration.withRegistrationId("my-id")
.entityId("{baseUrl}/{registrationId}")
.decryptionX509Credentials(c -> c.add(relyingPartyDecryptingCredential()))
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyMetadata(party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials(c -> c.add(assertingPartyVerifyingCredential()))
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
)
.build();
val relyingPartyRegistration =
RelyingPartyRegistration.withRegistrationId("my-id")
.entityId("{baseUrl}/{registrationId}")
.decryptionX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(relyingPartyDecryptingCredential())
}
.assertionConsumerServiceLocation("/my-login-endpoint/{registrationId}")
.assertingPartyMetadata { party -> party
.entityId("https://ap.example.org")
.verificationX509Credentials { c -> c.add(assertingPartyVerifyingCredential()) }
.singleSignOnServiceLocation("https://ap.example.org/SSO.saml2")
}
.build()
最上位のメタデータメソッドは、証明書利用者に関する詳細です。 |
依存パーティが SAML レスポンスを期待している場所は、アサーションコンシューマーサービスの場所です。 |
証明書利用者の entityId のデフォルトは {baseUrl}/saml2/service-provider-metadata/{registrationId} です。これは、アサーティングパーティが依存パーティについて知るように設定するときに必要なこの値です。
assertionConsumerServiceLocation のデフォルトは /login/saml2/sso/{registrationId} です。デフォルトでは、フィルターチェーンの Saml2WebSsoAuthenticationFilter にマップされます。
URI パターン
前の例で {baseUrl} および {registrationId} プレースホルダーに気づいたかもしれません。
これらは URI の生成に役立ちます。その結果、証明書利用者の entityId および assertionConsumerServiceLocation は、次のプレースホルダーをサポートします。
baseUrl- デプロイされたアプリケーションのスキーム、ホスト、ポートregistrationId- この依存パーティの登録 IDbaseScheme- デプロイされたアプリケーションのスキームbaseHost- デプロイされたアプリケーションのホストbasePort- デプロイされたアプリケーションのポート
例: 以前に定義された assertionConsumerServiceLocation は次のとおりです。
/my-login-endpoint/{registrationId}
デプロイされたアプリケーションでは、次のように変換されます。
/my-login-endpoint/adfs
前に示した entityId は、次のように定義されています。
{baseUrl}/{registrationId}
デプロイされたアプリケーションでは、これは次のように変換されます。
https://rp.example.com/adfs
一般的な URI パターンは次のとおりです。
/saml2/authenticate/{registrationId}- そのRelyingPartyRegistrationの構成に基づいて<saml2:AuthnRequest>を生成し、それをアサーティングパーティに送信するエンドポイント/login/saml2/sso/- アサーティングパーティの<saml2:Response>を認証するエンドポイント。RelyingPartyRegistrationは、必要に応じて、以前に認証された状態またはレスポンスの発行者から検索されます。/login/saml2/sso/{registrationId}もサポート/logout/saml2/sso-<saml2:LogoutRequest>および<saml2:LogoutResponse>ペイロードを処理するエンドポイント。RelyingPartyRegistrationは、必要に応じて、以前に認証された状態またはリクエストの発行者から検索されます。/logout/saml2/slo/{registrationId}もサポート/saml2/metadata- 一連のRelyingPartyRegistrationの証明書利用者メタデータ。特定のRelyingPartyRegistrationの/saml2/metadata/{registrationId}または/saml2/service-provider-metadata/{registrationId}もサポートします
registrationId は RelyingPartyRegistration の主要な識別子であるため、認証されていないシナリオの URL で必要になります。何らかの理由で URL から registrationId を削除したい場合は、RelyingPartyRegistrationResolver を指定して Spring Security に registrationId を検索する方法を伝えることができます。
資格情報
前に示した例では、使用されたクレデンシャルにも気付いた可能性があります。
多くの場合、証明書利用者は同じキーを使用してペイロードに署名し、復号化します。または、同じキーを使用してペイロードを検証し、暗号化することもできます。
このため、Spring Security には Saml2X509Credential が同梱されており、これは SAML 固有の認証情報であり、さまざまなユースケースで同じキーを簡単に構成できます。
少なくとも、アサーティングパーティの署名されたレスポンスを検証できるように、アサーティングパーティからの証明書が必要です。
アサーションパーティからのアサーションを検証するために使用できる Saml2X509Credential を構築するには、ファイルをロードして CertificateFactory を使用できます。
Java
Kotlin
Resource resource = new ClassPathResource("ap.crt");
try (InputStream is = resource.getInputStream()) {
X509Certificate certificate = (X509Certificate)
CertificateFactory.getInstance("X.509").generateCertificate(is);
return Saml2X509Credential.verification(certificate);
}
val resource = ClassPathResource("ap.crt")
resource.inputStream.use {
return Saml2X509Credential.verification(
CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate?
)
}
アサーション側もアサーションを暗号化するとします。その場合、証明書利用者は暗号化された値を復号化するために秘密鍵を必要とします。
その場合、RSAPrivateKey とそれに対応する X509Certificate が必要です。1 つ目は Spring Security の RsaKeyConverters ユーティリティクラスを使用してロードでき、2 つ目は以前と同じようにロードできます。
Java
Kotlin
X509Certificate certificate = relyingPartyDecryptionCertificate();
Resource resource = new ClassPathResource("rp.crt");
try (InputStream is = resource.getInputStream()) {
RSAPrivateKey rsa = RsaKeyConverters.pkcs8().convert(is);
return Saml2X509Credential.decryption(rsa, certificate);
}
val certificate: X509Certificate = relyingPartyDecryptionCertificate()
val resource = ClassPathResource("rp.crt")
resource.inputStream.use {
val rsa: RSAPrivateKey = RsaKeyConverters.pkcs8().convert(it)
return Saml2X509Credential.decryption(rsa, certificate)
}
これらのファイルの場所を適切な Spring Boot プロパティとして指定すると、Spring Boot がこれらの変換を実行します。 |
証明書利用者構成の複製
アプリケーションが複数のアサーティングパーティを使用する場合、いくつかの構成が RelyingPartyRegistration インスタンス間で複製されます。
依存パーティの
entityIdその
assertionConsumerServiceLocationその資格情報 — たとえば、署名または復号化のクレデンシャル
この設定により、一部の ID プロバイダーと他の ID プロバイダーのクレデンシャルをより簡単にローテーションできる場合があります。
重複はいくつかの方法で軽減できます。
まず、YAML では、これは参照によって軽減できます。
spring:
security:
saml2:
relyingparty:
registration:
okta:
signing.credentials: &relying-party-credentials
- private-key-location: classpath:rp.key
certificate-location: classpath:rp.crt
assertingparty:
entity-id: ...
azure:
signing.credentials: *relying-party-credentials
assertingparty:
entity-id: ... 次に、データベースでは、RelyingPartyRegistration のモデルを複製する必要はありません。
第 3 に、Java では、カスタム構成メソッドを作成できます。
Java
Kotlin
private RelyingPartyRegistration.Builder
addRelyingPartyDetails(RelyingPartyRegistration.Builder builder) {
Saml2X509Credential signingCredential = ...
builder.signingX509Credentials(c -> c.addAll(signingCredential));
// ... other relying party configurations
}
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() {
RelyingPartyRegistration okta = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("okta")).build();
RelyingPartyRegistration azure = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("azure")).build();
return new InMemoryRelyingPartyRegistrationRepository(okta, azure);
}
private fun addRelyingPartyDetails(builder: RelyingPartyRegistration.Builder): RelyingPartyRegistration.Builder {
val signingCredential: Saml2X509Credential = ...
builder.signingX509Credentials { c: MutableCollection<Saml2X509Credential?> ->
c.add(
signingCredential
)
}
// ... other relying party configurations
}
@Bean
open fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
val okta = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("okta")
).build()
val azure = addRelyingPartyDetails(
RelyingPartyRegistrations
.fromMetadataLocation(oktaMetadataUrl)
.registrationId("azure")
).build()
return InMemoryRelyingPartyRegistrationRepository(okta, azure)
}
リクエストからの RelyingPartyRegistration の解決
これまで見てきたように、Spring Security は URI パスで登録 ID を探すことによって RelyingPartyRegistration を解決します。
ユースケースに応じて、他の多くの戦略を使用して 1 つを導き出します。例:
<saml2:Response>を処理する場合、RelyingPartyRegistrationは関連する<saml2:AuthRequest>または<saml2:Response#Issuer>要素から検索されます。<saml2:LogoutRequest>を処理するために、RelyingPartyRegistrationは現在ログインしているユーザーまたは<saml2:LogoutRequest#Issuer>要素から検索されます。メタデータを公開する場合、
RelyingPartyRegistrationはIterable<RelyingPartyRegistration>も実装しているリポジトリから検索されます。
これを調整する必要がある場合は、これをカスタマイズすることを目的としたこれらの各エンドポイントの特定のコンポーネントに目を向けることができます。
SAML レスポンスの場合は、
AuthenticationConverterをカスタマイズしますログアウトリクエストの場合、
Saml2LogoutRequestValidatorParametersResolverをカスタマイズしますメタデータについては、
Saml2MetadataResponseResolverをカスタマイズします
連携ログイン
SAML 2.0 の一般的な取り決めの 1 つは、複数のアサーティングパーティを持つ ID プロバイダーです。この場合、ID プロバイダーのメタデータエンドポイントは複数の <md:IDPSSODescriptor> 要素を返します。
これらの複数のアサーティングパーティは、次のように RelyingPartyRegistrations への 1 回の呼び出しでアクセスできます。
Java
Kotlin
Collection<RelyingPartyRegistration> registrations = RelyingPartyRegistrations
.collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml")
.stream().map((builder) -> builder
.registrationId(UUID.randomUUID().toString())
.entityId("https://example.org/saml2/sp")
.build()
)
.collect(Collectors.toList());
var registrations: Collection<RelyingPartyRegistration> = RelyingPartyRegistrations
.collectionFromMetadataLocation("https://example.org/saml2/idp/metadata.xml")
.stream().map { builder : RelyingPartyRegistration.Builder -> builder
.registrationId(UUID.randomUUID().toString())
.entityId("https://example.org/saml2/sp")
.assertionConsumerServiceLocation("{baseUrl}/login/saml2/sso")
.build()
}
.collect(Collectors.toList())
登録 ID がランダムな値に設定されているため、これにより特定の SAML 2.0 エンドポイントが予測不能になることに注意してください。これに対処するにはいくつかの方法があります。フェデレーションの特定のユースケースに適した方法に焦点を当てましょう。
多くのフェデレーションの場合、すべてのアサーティングパーティがサービスプロバイダーの設定を共有します。Spring Security がデフォルトでサービスプロバイダーのメタデータに registrationId を含めることを考えると、もう 1 つのステップは、対応する URI を変更して registrationId を除外することです。これは、entityId と assertionConsumerServiceLocation が静的エンドポイントで構成されている上記のサンプルですでに行われていることがわかります。
この完全な例は、saml-extension-federation サンプル [GitHub] (英語) で確認できます。
Spring Security SAML 拡張 URI の使用
Spring Security SAML 拡張機能から移行する場合、SAML 拡張 URI のデフォルトを使用するようにアプリケーションを構成すると、利点が得られる場合があります。
詳細については、custom-urls サンプル [GitHub] (英語) および saml-extension-federation サンプル [GitHub] (英語) を参照してください。