Performing Single Logout
Spring Security 附带了对 RP- 和 AP- 发起的 SAML 2.0 单点注销的支持。 简而言之,Spring Security 支持以下两种用例:
-
RP-Initiated- 您的应用程序有一个端点,在对其执行 POST 操作时,它会注销用户并发送 `saml2:LogoutRequest`给声明方。此后,声明方将回送一个 `saml2:LogoutResponse`并允许您的应用程序响应
-
AP-Initiated- 您的应用程序有一个端点,将会从声明方接收到
saml2:LogoutRequest
。那时您的应用程序将完成注销,然后向声明方发送saml2:LogoutResponse
。
在 AP-Initiated 场景中,应用程序在退出后将要执行的任何本地重定向都将变得毫无意义。一旦应用程序发送 |
Minimal Configuration for Single Logout
要使用 Spring Security 的 SAML 2.0 单点注销功能,您需要以下内容:
-
首先,声明方必须支持 SAML 2.0 单点注销
-
其次,声明方应当被配置为对 `saml2:LogoutRequest`和 `saml2:LogoutResponse`进行签名和 POST,对您的应用程序的 `/logout/saml2/slo`端点进行签名和 POST
-
第三,您的应用程序必须有一个用于签名 `saml2:LogoutRequest`和 `saml2:LogoutResponse`的 PKCS#8 私钥和 X.509 证书
您可以从初始最小示例开始,并添加以下配置:
@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`实例或 multiple instances |
2 | - 其次,表明您的应用程序希望使用 SAML SLO 来注销最终用户 |
Runtime Expectations
给定以上配置,任何已登录的用户都可以向您的应用程序发送 POST /logout
来执行 RP 发起的 SLO。然后,您的应用程序将执行以下操作:
-
注销用户并使会话无效
-
使用
Saml2LogoutRequestResolver`创建、签名并序列化与当前登录用户关联的 `<saml2:LogoutRequest>
。 -
根据
RelyingPartyRegistration
向声明方发送重定向或帖子 -
对声明方发送的 `<saml2:LogoutResponse>`进行反序列化、验证和处理
-
重定向到任何配置成功的注销端点
此外,如果断言方向 /logout/saml2/slo
发送 <saml2:LogoutRequest>
时,您的应用程序也可以参与 AP 发起的注销操作:
-
使用 `Saml2LogoutRequestHandler`对声明方发送的 `<saml2:LogoutRequest>`进行反序列化、验证和处理
-
注销用户并使会话无效
-
创建、签名并序列化基于刚注销的用户关联的
RelyingPartyRegistration
的<saml2:LogoutResponse>
。 -
根据
RelyingPartyRegistration
向声明方发送重定向或帖子
添加 |
Configuring Logout Endpoints
有三个行为可由不同的端点触发:
-
RP 发起的注销,允许经过身份验证的用户 `POST`并通过向声明方发送 `<saml2:LogoutRequest>`触发注销过程
-
AP 发起的注销,允许声明方向应用程序发送
<saml2:LogoutRequest>
-
AP 注销响应,它允许声明方对 RP 发起的
<saml2:LogoutRequest>`发送 `<saml2:LogoutResponse>
第一个是在主体类型为 Saml2AuthenticatedPrincipal
时执行常规 POST /logout
触发。
第二个由用宣告方签名的 SAMLRequest
POST 到 /logout/saml2/slo
终结点触发。
第三个由用宣告方签名的 SAMLResponse
POST 到 /logout/saml2/slo
终结点触发。
因为用户已经登陆或原始的登出请求是已知的,所以 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
中配置这些终结点。
Customizing <saml2:LogoutRequest>
Resolution
通常需要在 <saml2:LogoutRequest>
中设置除 Spring Security 提供的默认值之外的其他值。
默认情况下,Spring Security 将发出 <saml2:LogoutRequest>
并提供:
-
Destination`属性 - 来自 `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation
-
`ID`属性 - GUID
-
<Issuer>`元素 - 来自 `RelyingPartyRegistration#getEntityId
-
<NameID>`元素 - 来自 `Authentication#getName
若要添加其他值,你可以使用委托,如下所示:
@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)
)
);
Customizing <saml2:LogoutResponse>
Resolution
通常需要在 <saml2:LogoutResponse>
中设置除 Spring Security 提供的默认值之外的其他值。
默认情况下,Spring Security 将发出 <saml2:LogoutResponse>
并提供:
-
Destination`属性 - 来自 `RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation
-
`ID`属性 - GUID
-
<Issuer>`元素 - 来自 `RelyingPartyRegistration#getEntityId
-
<Status>`元素 - `SUCCESS
若要添加其他值,你可以使用委托,如下所示:
@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)
)
);
Customizing <saml2:LogoutRequest>
Authentication
若要自定义验证,你可以实现你自己的 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)
)
);
Customizing <saml2:LogoutResponse>
Authentication
若要自定义验证,你可以实现你自己的 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)
)
);
Customizing <saml2:LogoutRequest>
storage
当你的应用发送 <saml2:LogoutRequest>
时,该值储存在会话中,以便可以验证 RelayState
参数和 <saml2:LogoutResponse>
中的 InResponseTo
属性。
如果你想将在会话之外的某个地方存储登出请求,你可以如此在 DSL 中提供你的自定义实现:
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestRepository(myCustomLogoutRequestRepository)
)
);