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
に渡します。デフォルトでは、OpenSamlAuthenticationProvider
を使用します。
認証が失敗した場合、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>
<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
</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:
identityprovider:
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 は他のモジュールに比べて非常に小さいです。代わりに、OpenSamlAuthenticationRequestFactory
や OpenSamlAuthenticationProvider
などのクラスは、認証プロセスのさまざまなステップをカスタマイズする Converter
実装を公開します。
例: アプリケーションが SAMLResponse
を受信し、Saml2WebSsoAuthenticationFilter
に委譲すると、フィルターは OpenSamlAuthenticationProvider
に委譲します。
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:
okta:
signing.credentials: &relying-party-credentials
- private-key-location: classpath:rp.key
certificate-location: classpath:rp.crt
identityprovider:
entity-id: ...
azure:
signing.credentials: *relying-party-credentials
identityprovider:
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>`s, the `RelyingPartyRegistration
は関連する<saml2:AuthRequest>
または<saml2:Response#Issuer>
要素から検索されます。<saml2:LogoutRequest>`s, the `RelyingPartyRegistration
を処理するために、現在ログインしているユーザーまたは<saml2:LogoutRequest#Issuer>
要素から検索されますメタデータを公開する場合、
RelyingPartyRegistration`s are looked up from any repository that also implements `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] (英語) を参照してください。