最新の安定バージョンについては、Spring Security 7.0.5 を使用してください!

Saml 2.0 移行

デフォルトで OpenSAML 5 を使用する

OpenSAML 4.x は OpenSAML チームによるサポートを終了しました。そのため、Spring Security では、すべてのケースにおいてデフォルトで OpenSaml5 コンポーネントが使用されます。

アプリケーションがこれにどの程度応答するかを確認するには、次の手順を実行します。

  1. OpenSAML の依存関係を 5.x に更新する

  2. OpenSaml4XXX Spring Security コンポーネントを構築する場合は、OpenSaml5 に変更します。

オプトインできない場合は、opensaml-saml-api および opensaml-saml-impl の 4.x 依存関係を追加し、spring-security-saml2-service-provider から 5.x 依存関係を除外します。

依存パーティが見つからない場合はフィルターチェーンを続行する

Spring Security 6 では、リクエスト URI が一致しても、証明書利用者の登録が見つからない場合、Saml2WebSsoAuthenticationFilter は例外をスローします。

アプリケーションがこれをエラー状況と見なさないケースは数多くあります。たとえば、このフィルターは、AuthorizationFilter が証明書利用者が存在しない場合にどのように応答するかを知りません。場合によっては、エラーとして許容されることもあります。

その他の場合には、AuthenticationEntryPoint を呼び出すことが必要になる場合があります。これは、このフィルターがリクエストを AuthorizationFilter に続行することを許可した場合に発生します。

このフィルターの柔軟性を向上させるために、Spring Security 7 では、証明書利用者登録が見つからない場合、例外をスローするのではなく、フィルターチェーンを続行します。

多くのアプリケーションにとって、唯一の注目すべき変更点は、証明書利用者の登録が見つからない場合に authenticationEntryPoint が呼び出されることです。アサーションパーティが 1 つしかない場合、これはデフォルトで新しい認証リクエストが作成され、アサーションパーティに返送されることを意味します。これにより、「Too Many Redirects(リダイレクト過多)」ループが発生する可能性があります。

このように影響を受けるかどうかを確認するには、Saml2WebSsoAuthenticationFilter で次のプロパティを設定して、6 でのこの変更に備えることができます。

  • Java

  • Kotlin

  • XML

http
    .saml2Login((saml2) -> saml2
        .withObjectPostProcessor(new ObjectPostProcessor<Saml2WebSsoAuhenticaionFilter>() {
			@Override
            public Saml2WebSsoAuthenticationFilter postProcess(Saml2WebSsoAuthenticationFilter filter) {
				filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true);
				return filter;
            }
        })
    )
http {
    saml2Login { }
    withObjectPostProcessor(
        object : ObjectPostProcessor<Saml2WebSsoAuhenticaionFilter?>() {
            override fun postProcess(filter: Saml2WebSsoAuthenticationFilter): Saml2WebSsoAuthenticationFilter {
            filter.setContinueChainWhenNoRelyingPartyRegistrationFound(true)
            return filter
        }
    })
}
<b:bean id="saml2PostProcessor" class="org.example.MySaml2WebSsoAuthenticationFilterBeanPostProcessor"/>

アサーションを検証した後、レスポンスを検証する

Spring Security 6 では、<saml2:Response> を認証する順序は次のとおりです。

  1. レスポンス署名を確認する(ある場合)

  2. レスポンスを復号する

  3. 宛先や発行者などのレスポンス属性を検証する

  4. 各アサーションについて、署名を検証し、復号化し、フィールドを検証します。

  5. レスポンスに名前フィールドを持つアサーションが少なくとも 1 つ含まれていることを確認します

一部のレスポンス検証はステップ 3 で実行され、一部のレスポンス検証はステップ 5 で実行されるため、この順序付けは課題となる場合があります。具体的には、アプリケーションに名前フィールドがなく、それを検証する必要がない場合に、このことが課題となります。

Spring Security 7 では、レスポンス検証をアサーション検証の後に移動し、2 つの個別の検証ステップ 3 と 5 を組み合わせることで、これが簡素化されます。これが完了すると、レスポンス検証では NameID 属性の存在がチェックされなくなり、ResponseAuthenticationConverter を使用してこれを実行するようになります。

これにより、Authentication インスタンスで NameID 要素を使用しないため検証を必要としない ResponseAuthenticationConverter のサポートが追加されます。

この動作を事前に有効にするには、次のように OpenSaml5AuthenticationProvider#setValidateResponseAfterAssertions から true を使用します。

  • Java

  • Kotlin

OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
provider.setValidateResponseAfterAssertions(true);
val provider = OpenSaml5AuthenticationProvider()
provider.setValidateResponseAfterAssertions(true)

これにより、認証手順が以下のように変更されます。

  1. レスポンス署名を確認する(ある場合)

  2. レスポンスを復号する

  3. 各アサーションについて、署名を検証し、復号化し、フィールドを検証します。

  4. 宛先や発行者などのレスポンス属性を検証する

カスタムレスポンス認証コンバーターを使用している場合は、必要に応じて NameID 要素が存在するかどうかを確認する責任は、開発者自身にあることに注意してください。

レスポンス認証コンバーターを更新する代わりに、次のように NameID 要素のチェックを再度追加するカスタム ResponseValidator を指定することもできます。

  • Java

  • Kotlin

OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
provider.setValidateResponseAfterAssertions(true);
ResponseValidator responseValidator = ResponseValidator.withDefaults((responseToken) -> {
	Response response = responseToken.getResponse();
	Assertion assertion = CollectionUtils.firstElement(response.getAssertions());
	Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
            "Assertion [" + firstAssertion.getID() + "] is missing a subject");
	Saml2ResponseValidationResult failed = Saml2ResponseValidationResult.failure(error);
	if (assertion.getSubject() == null) {
		return failed;
	}
	if (assertion.getSubject().getNameID() == null) {
		return failed;
	}
	if (assertion.getSubject().getNameID().getValue() == null) {
		return failed;
	}
	return Saml2ResponseValidationResult.success();
});
provider.setResponseValidator(responseValidator);
val provider = OpenSaml5AuthenticationProvider()
provider.setValidateResponseAfterAssertions(true)
val responseValidator = ResponseValidator.withDefaults { responseToken: ResponseToken ->
	val response = responseToken.getResponse()
	val assertion = CollectionUtils.firstElement(response.getAssertions())
	val error = Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
        "Assertion [" + firstAssertion.getID() + "] is missing a subject")
	val failed = Saml2ResponseValidationResult.failure(error)
	if (assertion.getSubject() == null) {
        return@withDefaults failed
	}
	if (assertion.getSubject().getNameID() == null) {
		return@withDefaults failed
	}
	if (assertion.getSubject().getNameID().getValue() == null) {
		return@withDefaults failed
	}
	return@withDefaults Saml2ResponseValidationResult.success()
}
provider.setResponseValidator(responseValidator)

RelyingPartyRegistration の改善

RelyingPartyRegistration は、依拠当事者からのメタデータを主張当事者からのメタデータにリンクします。

API の改善に備えるため、以下の手順を実行してください。

  1. RelyingPartyRegistration#withRelyingPartyRegistration を使用して登録を変更する場合は、代わりに RelyingPartyRegistration#mutate を呼び出してください。

  2. AssertingPartyDetails を提供または取得する場合は、代わりに getAssertingPartyMetadata または withAssertingPartyMetadata を使用してください。

OpenSaml5AuthenticationProvider の改善

Spring Security 7 では、OpenSaml5AuthenticationProvider からいくつかの静的ファクトリを削除し、内部クラスを採用します。これらの内部クラスにより、レスポンスバリデーター、アサーションバリデーター、レスポンス認証コンバーターのカスタマイズが簡素化されます。

レスポンス検証

代わりに:

  • Java

  • Kotlin

@Bean
OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
	saml2.setResponseValidator((responseToken) -> OpenSamlAuthenticationProvider.createDefaultResponseValidator()
            .andThen((result) -> result
                .concat(myCustomValidator.convert(responseToken))
            ));
	return saml2;
}
@Bean
fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
	val saml2 = OpenSaml5AuthenticationProvider()
	saml2.setResponseValidator { responseToken -> OpenSamlAuthenticationProvider.createDefaultResponseValidator()
        .andThen { result -> result
            .concat(myCustomValidator.convert(responseToken))
        }
    }
	return saml2
}

OpenSaml5AuthenticationProvider.ResponseValidator を使用する:

  • Java

  • Kotlin

@Bean
OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
	saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator));
	return saml2;
}
@Bean
fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
	val saml2 = OpenSaml5AuthenticationProvider()
	saml2.setResponseValidator(ResponseValidator.withDefaults(myCustomValidator))
	return saml2
}

アサーション検証

代わりに:

  • Java

  • Kotlin

@Bean
OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
    authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider
        .createDefaultAssertionValidatorWithParameters(assertionToken -> {
            Map<String, Object> params = new HashMap<>();
            params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());
            // ... other validation parameters
            return new ValidationContext(params);
        })
    );
	return saml2;
}
@Bean
fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
	val saml2 = OpenSaml5AuthenticationProvider()
    authenticationProvider.setAssertionValidator(OpenSaml5AuthenticationProvider
        .createDefaultAssertionValidatorWithParameters { ->
            val params = HashMap<String, Object>()
            params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis())
            // ... other validation parameters
            return ValidationContext(params)
        }
    )
	return saml2
}

OpenSaml5AuthenticationProvider.AssertionValidator を使用する:

  • Java

  • Kotlin

@Bean
OpenSaml5AuthenticationProvider saml2AuthenticationProvider() {
	OpenSaml5AuthenticationProvider saml2 = new OpenSaml5AuthenticationProvider();
	Duration tenMinutes = Duration.ofMinutes(10);
    authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build());
	return saml2;
}
@Bean
fun saml2AuthenticationProvider(): OpenSaml5AuthenticationProvider {
	val saml2 = OpenSaml5AuthenticationProvider()
	val tenMinutes = Duration.ofMinutes(10)
    authenticationProvider.setAssertionValidator(AssertionValidator.builder().clockSkew(tenMinutes).build())
	return saml2
}

レスポンス認証コンバーター

代わりに:

  • Java

  • Kotlin

@Bean
Converter<ResponseToken, Saml2Authentication> authenticationConverter() {
	return (responseToken) -> {
		Saml2Authentication authentication = OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter()
            .convert(responseToken);
		// ... work with OpenSAML's Assertion object to extract the principal
		return new Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities());
	};
}
@Bean
fun authenticationConverter(): Converter<ResponseToken, Saml2Authentication> {
    return { responseToken ->
        val authentication =
            OpenSaml5AutnenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken)
		// ... work with OpenSAML's Assertion object to extract the principal
		return Saml2Authentication(myPrincipal, authentication.getSaml2Response(), authentication.getAuthorities())
    }
}

OpenSaml5AuthenticationProvider.ResponseAuthenticationConverter を使用する:

  • Java

  • Kotlin

@Bean
ResponseAuthenticationConverter authenticationConverter() {
	ResponseAuthenticationConverter authenticationConverter = new ResponseAuthenticationConverter();
	authenticationConverter.setPrincipalNameConverter((assertion) -> {
		// ... work with OpenSAML's Assertion object to extract the principal
	});
	return authenticationConverter;
}
@Bean
fun authenticationConverter(): ResponseAuthenticationConverter {
    val authenticationConverter = ResponseAuthenticationConverter()
    authenticationConverter.setPrincipalNameConverter { assertion ->
		// ... work with OpenSAML's Assertion object to extract the principal
    }
    return authenticationConverter
}