|
对于最新的稳定版本,请使用 Spring Security 7.0.4! |
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中,Saml2WebSsoAuthenticationFilter在请求URI匹配但未找到依赖方注册时会抛出异常。
当应用程序不认为这是一个错误情况时,有多种情形。
例如,此过滤器不知道AuthorizationFilter对缺少依赖方将如何响应。
在某些情况下,这可能是允许的。
在其他情况下,您可能希望调用AuthenticationEntryPoint,如果此过滤器允许请求继续传递给AuthorizationFilter时就会发生这种情况。
要提高该过滤器的灵活性,在Spring Security 7中,如果未找到依赖方注册信息,则将继续过滤器链,而不是抛出异常。
对于许多应用程序,唯一的显著变化将是:如果依赖方注册无法找到,则会调用你的authenticationEntryPoint。
当你只有一个声明方时,默认情况下会生成一个新的认证请求并将其发送回声明方,这可能会导致“重定向过多”的循环。
要查看是否受到影响,您可以在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中,对
-
验证响应签名(如果有的话)
-
解密响应
-
验证响应属性,例如目标和发行者
-
对于每个断言,请先验证签名、解密,然后再验证其字段
-
检查响应以确保至少有一个断言具有名称字段
这有时会带来挑战,因为某些响应验证在第3步进行,而另一些则在第5步进行。 具体来说,当应用程序没有名称字段并且不需要验证它时,这种情况就会带来挑战。
在 Spring Security 7 中,通过将响应验证移至断言验证之后并结合两个单独的验证步骤 3 和 5 而使这一过程得到了简化。
当这些步骤完成后,响应验证将不再检查 NameID 属性的存在性,并依赖于 ResponseAuthenticationConverters 来完成这一任务。
这将添加支持不使用ResponseAuthenticationConverter实例中的NameID元素的Authentications,并且因此不需要验证它。
要提前启用此行为,请使用OpenSaml5AuthenticationProvider#setValidateResponseAfterAssertions设置为true,如下所示:
-
Java
-
Kotlin
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
provider.setValidateResponseAfterAssertions(true);
val provider = OpenSaml5AuthenticationProvider()
provider.setValidateResponseAfterAssertions(true)
这将改变认证步骤如下:
-
验证响应签名(如果有的话)
-
解密响应
-
对于每个断言,请先验证签名、解密,然后再验证其字段
-
验证响应属性,例如目标和发行者
注意,如果您自定义了响应认证转换器,则在需要时必须自行检查NameID元素是否存在。
除了更新您的响应认证转换器外,您还可以指定一个自定义的ResponseValidator,以重新添加对NameID元素的检查,如下所示:
-
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的一些改进做准备,请执行以下步骤:
-
如果通过使用
RelyingPartyRegistration#withRelyingPartyRegistration来修改注册信息,请改为调用RelyingPartyRegistration#mutate。 -
如果提供或检索
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
}