执行单次(Single Logout)注销
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
Spring Security支持RP和AP发起的SAML 2.0单一(Single Logout)注销。
简而言之,Spring Security支持两种使用情况。
-
RP发起 - 你的应用程序有一个端点,当被POST到时,将注销用户并发送一个
saml2:LogoutRequest
给断言方。此后,断言方将发回一个saml2:LogoutResponse
,并允许你的应用程序响应 -
AP发起 - 你的应用程序有一个端点,将从断言方收到一个
saml2:LogoutRequest
。你的应用程序将在这时完成它的注销,然后发送一个saml2:LogoutResponse
给断言方。
在AP发起的情况下,你的应用程序在注销后所做的任何本地重定向都将失去意义。一旦你的应用程序发送了一个 saml2:LogoutResponse ,它就不再有对浏览器的控制。
|
单次注销的最小化配置
要使用Spring Security的SAML 2.0单一注销功能,你需要以下东西。
-
首先,断言方必须支持SAML 2.0单次注销。
-
第二,断言方应被配置为签署并将
saml2:LogoutRequest
和saml2:LogoutResponse
POST 到你的应用程序的/logout/saml2/slo
端点。 -
第三,你的应用程序必须有一个 PKCS#8 私钥和 X.509 证书来签署
saml2:LogoutRequest
和saml2:LogoutResponse
。
你可以从最初的最小例子开始,添加以下配置。
@Value("${private.key}") RSAPrivateKey key;
@Value("${public.certificate}") X509Certificate certificate;
@Bean
RelyingPartyRegistrationRepository registrations() {
Saml2X509Credential credential = Saml2X509Credential.signing(key, certificate);
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("id")
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")
.signingX509Credentials((signing) -> signing.add(credential)) (1)
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
}
@Bean
SecurityFilterChain web(HttpSecurity http, RelyingPartyRegistrationRepository registrations) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.saml2Login(withDefaults())
.saml2Logout(withDefaults()); (2)
return http.build();
}
1 | 首先,将你的签名密钥添加到 RelyingPartyRegistration 实例或 多个实例中。 |
2 | 第二,表明你的应用程序要使用SAML SLO来注销终端用户。 |
Runtime Expectation
鉴于上述配置,任何登录的用户都可以向你的应用程序发送一个 POST /logout
,以执行RP发起的SLO。然后你的应用程序将做以下工作。
-
注销用户并使会话无效。
-
使用
Saml2LogoutRequestResolver
来创建、签署和序列化一个<saml2:LogoutRequest>
,基于与当前登录的用户相关的RelyingPartyRegistration
。 -
根据
RelyingPartyRegistration
,向断言方发送一个重定向(redirect)或post。 -
对断言方发送的
<saml2:LogoutResponse>
进行反序列化、验证和处理。 -
重定向到任何配置好的成功注销端点。
另外,当断言方发送 <saml2:LogoutRequest>
到 /logout/saml2/slo
时,你的应用程序可以参与AP发起的注销。
-
使用
Saml2LogoutRequestHandler
来反序列化、验证和处理断言方发送的<saml2:LogoutRequest>
。 -
注销用户并使会话无效。
-
创建、签署和序列化一个
<saml2:LogoutResponse>
,基于与刚登出的用户相关的RelyingPartyRegistration
。 -
根据
RelyingPartyRegistration
,向断言方发送一个redirect或POST。
添加 saml2Logout 可以为服务提供者增加注销的能力。因为它是一个可选的能力,你需要为每个独立的 RelyingPartyRegistration 启用它。你可以通过设置 RelyingPartyRegistration.Builder#singleLogoutServiceLocation 属性来实现。
|
配置注销端点
有三种行为可以由不同的端点触发。
-
RP发起的注销,允许认证的用户通过向断言方发送
<saml2:LogoutRequest>
来POST
并触发注销过程。 -
AP发起的注销,允许断言方向应用程序发送
<saml2:LogoutRequest>
。 -
AP注销响应,允许断言方发送
<saml2:LogoutResponse>
以响应RP发起的<saml2:LogoutRequest>
。
当委托人(principal)是 Saml2AuthenticatedPrincipal
类型时,第一个是通过执行正常的 POST /logout
来触发的。
第二种是通过向 /logout/saml2/slo
端点发送由断言方签名的 SAMLRequest
来触发。
第三种是通过向 /logout/saml2/slo
端点发送由断言方签名的 SAMLResponse
来触发的。
因为用户已经登录或者原始的注销请求是已知的,所以 registrationId
已经是已知的。出于这个原因,{registrationId}
默认不是这些URL的一部分。
这个URL在DSL中是可以定制的。
例如,如果你正在将现有的依赖方迁移到 Spring Security,你的断言方可能已经指向 GET /SLOService.saml2
。为了减少断言方的配置变化,你可以在DSL中这样配置过滤器。
-
Java
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
);
你也应该在你的 RelyingPartyRegistration
中配置这些端点。
自定义 <saml2:LogoutRequest>
解析
在 <saml2:LogoutRequest>
中,除了 Spring Security 提供的默认值外,通常需要设置其他值。
默认情况下,Spring Security 会发布一个 <saml2:LogoutRequest>
并提供。
-
Destination
属性 - 来自RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation
-
ID
属性 - 一个GUID -
<Issuer>
元素 - 来自RelyingPartyRegistration#getEntityId
-
<NameID>
元素 - 来自Authentication#getName
要添加其他值,你可以使用委托(delegation),像这样。
@Bean
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationRepository registrations) {
OpenSaml4LogoutRequestResolver logoutRequestResolver =
new OpenSaml4LogoutRequestResolver(registrations);
logoutRequestResolver.setParametersConsumer((parameters) -> {
String name = ((Saml2AuthenticatedPrincipal) parameters.getAuthentication().getPrincipal()).getFirstAttribute("CustomAttribute");
String format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient";
LogoutRequest logoutRequest = parameters.getLogoutRequest();
NameID nameId = logoutRequest.getNameID();
nameId.setValue(name);
nameId.setFormat(format);
});
return logoutRequestResolver;
}
然后,你可以在DSL中提供你自定义的 Saml2LogoutRequestResolver
,如下所示。
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestResolver(this.logoutRequestResolver)
)
);
自定义 <saml2:LogoutResponse>
解析
在 <saml2:LogoutResponse>
中,除了Spring Security 提供的默认值外,通常需要设置其他值。
默认情况下,Spring Security会发布一个 <saml2:LogoutResponse>
并提供:
-
Destination
属性 - 来自RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation
-
ID
属性 - GUID -
<Issuer>
元素 - 来自RelyingPartyRegistration#getEntityId
-
<Status>
元素 -SUCCESS
要添加其他值,你可以使用委托(delegation),像这样。
@Bean
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationRepository registrations) {
OpenSaml4LogoutResponseResolver logoutRequestResolver =
new OpenSaml4LogoutResponseResolver(registrations);
logoutRequestResolver.setParametersConsumer((parameters) -> {
if (checkOtherPrevailingConditions(parameters.getRequest())) {
parameters.getLogoutRequest().getStatus().getStatusCode().setCode(StatusCode.PARTIAL_LOGOUT);
}
});
return logoutRequestResolver;
}
然后,你可以在DSL中提供你自定义的 Saml2LogoutResponseResolver
,如下所示。
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestResolver(this.logoutRequestResolver)
)
);
自定义 <saml2:LogoutRequest>
认证
为了定制认证,你可以实现你自己的 Saml2LogoutRequestValidator
。在这一点上,验证是最小的,所以你可以先委托给默认的 Saml2LogoutRequestValidator
,像这样。
@Component
public class MyOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator {
private final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();
@Override
public Saml2LogoutRequestValidator logout(Saml2LogoutRequestValidatorParameters parameters) {
// verify signature, issuer, destination, and principal name
Saml2LogoutValidatorResult result = delegate.authenticate(authentication);
LogoutRequest logoutRequest = // ... parse using OpenSAML
// perform custom validation
}
}
然后,你可以在DSL中提供你自定义的 Saml2LogoutRequestValidator
,如下所示。
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestAuthenticator(myOpenSamlLogoutRequestAuthenticator)
)
);
自定义 <saml2:LogoutResponse>
认证
为了定制验证,你可以实现你自己的 Saml2LogoutResponseValidator
。在这一点上,验证是最小的,所以你可以先委托给默认的 Saml2LogoutResponseValidator
,像这样。
@Component
public class MyOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidator {
private final Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();
@Override
public Saml2LogoutValidatorResult logout(Saml2LogoutResponseValidatorParameters parameters) {
// verify signature, issuer, destination, and status
Saml2LogoutValidatorResult result = delegate.authenticate(parameters);
LogoutResponse logoutResponse = // ... parse using OpenSAML
// perform custom validation
}
}
然后,你可以在DSL中提供你自定义的 Saml2LogoutResponseValidator
,如下所示。
http
.saml2Logout((saml2) -> saml2
.logoutResponse((response) -> response
.logoutResponseAuthenticator(myOpenSamlLogoutResponseAuthenticator)
)
);
自定义 <saml2:LogoutRequest>
存储
当你的应用程序发送 <saml2:LogoutRequest>
时,该值被存储在会话中,以便 <saml2:LogoutResponse>
中的 RelayState
参数和 InResponseTo
属性可以被验证。
如果你想在会话之外的其他地方存储注销请求,你可以在DSL中提供你的自定义实现,像这样。
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestRepository(myCustomLogoutRequestRepository)
)
);