最新の安定バージョンについては、Spring Security 7.0.3 を使用してください! |
Saml 2.0 移行
デフォルトで OpenSAML 5 を使用する
OpenSAML 4.x は OpenSAML チームによるサポートを終了しました。そのため、Spring Security では、すべてのケースにおいてデフォルトで OpenSaml5 コンポーネントが使用されます。
アプリケーションがこれにどの程度応答するかを確認するには、次の手順を実行します。
OpenSAML の依存関係を 5.x に更新する
OpenSaml4XXXSpring 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 つ含まれていることを確認します
This ordering sometimes poses challenges since some response validation is being done in Step 3 and some in Step 5. Specifically, this poses a chellenge when an application doesn’t have a name field and doesn’t need it to be validated.
In Spring Security 7, this is simplified by moving response validation to after assertion validation and combining the two separate validation steps 3 and 5. When this is complete, response validation will no longer check for the existence of the NameID attribute and rely on ResponseAuthenticationConverters to do this.
This will add support ResponseAuthenticationConverters that don’t use the NameID element in their Authentication instance and so don’t need it validated.
To opt-in to this behavior in advance, use OpenSaml5AuthenticationProvider#setValidateResponseAfterAssertions to true like so:
Java
Kotlin
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
provider.setValidateResponseAfterAssertions(true);
val provider = OpenSaml5AuthenticationProvider()
provider.setValidateResponseAfterAssertions(true)
This will change the authentication steps as follows:
レスポンス署名を確認する(ある場合)
レスポンスを復号する
各アサーションについて、署名を検証し、復号化し、フィールドを検証します。
宛先や発行者などのレスポンス属性を検証する
Note that if you have a custom response authentication converter, then you are now responsible to check if the NameID element exists in the event that you need it.
Alternatively to updating your response authentication converter, you can specify a custom ResponseValidator that adds back in the check for the NameID element as follows:
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 links metadata from a relying party to metadata from an asserting party.
To prepare for some improvements to the API, please take the following steps:
If you are mutating a registration by using
RelyingPartyRegistration#withRelyingPartyRegistration, instead callRelyingPartyRegistration#mutateIf you are providing or retrieving
AssertingPartyDetails, usegetAssertingPartyMetadataorwithAssertingPartyMetadatainstead.
OpenSaml5AuthenticationProvider の改善
Spring Security 7 will remove a handful of static factories from OpenSaml5AuthenticationProvider in favor of inner classes. These inner classes simplify customization of the response validator, the assertion validator, and the response authentication converter.
Response Validation
代わりに:
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
}
use 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
}
Assertion Validation
代わりに:
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
}
use 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
}
Response Authentication Converter
代わりに:
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())
}
}
use 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
}