对于最新的稳定版本,请使用 Spring Security 6.3.1! |
对于最新的稳定版本,请使用 Spring Security 6.3.1! |
Spring Security 支持 RP 和 AP 启动的 SAML 2.0 单点注销。
简而言之,Spring Security 支持两种用例:
-
RP-Initiated - 应用程序具有一个终结点,当 POST 到该终结点时,该终结点将注销用户并向断言方发送一个终结点。 此后,主张方将发回 a 并允许您的申请做出回应
saml2:LogoutRequest
saml2:LogoutResponse
-
AP-Initiated - 应用程序具有一个终结点,该终结点将从断言方接收 AP-Initiated - 应用程序具有将从断言方接收的终结点。 此时,应用程序将完成注销,然后向断言方发送注销。
saml2:LogoutRequest
saml2:LogoutResponse
在 AP 启动的方案中,应用程序在注销后执行的任何本地重定向都变得毫无意义。
一旦应用程序发送 ,它就不再控制浏览器。saml2:LogoutResponse |
在 AP 启动的方案中,应用程序在注销后执行的任何本地重定向都变得毫无意义。
一旦应用程序发送 ,它就不再控制浏览器。saml2:LogoutResponse |
单点注销的最低配置
要使用 Spring Security 的 SAML 2.0 单点注销功能,您需要满足以下条件:
-
首先,断言方必须支持 SAML 2.0 单点注销
-
其次,应将断言方配置为对应用程序的终结点进行签名和开机处理
saml2:LogoutRequest
saml2:LogoutResponse
/logout/saml2/slo
-
第三,应用程序必须具有 PKCS#8 私钥和 X.509 证书,用于对 s 和 s 进行签名
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 注销最终用户 |
运行时期望
根据上述配置,任何登录用户都可以向应用程序发送 A 以执行 RP 启动的 SLO。
然后,应用程序将执行以下操作:POST /logout
-
注销用户并使会话失效
-
使用 a 基于与当前登录用户关联的
RelyingPartyRegistration
创建、签名和序列化 a。Saml2LogoutRequestResolver
<saml2:LogoutRequest>
-
根据
RelyingPartyRegistration
向断言方发送重定向或发布 -
反序列化、验证和处理断言方发送的内容
<saml2:LogoutResponse>
-
重定向到任何已配置的成功注销终结点
此外,当断言方将 AP 发送到以下位置时,您的应用程序可以参与 AP 发起的注销:<saml2:LogoutRequest>
/logout/saml2/slo
-
使用 a 反序列化、验证和处理断言方发送的
Saml2LogoutRequestHandler
<saml2:LogoutRequest>
-
注销用户并使会话失效
-
基于与刚刚注销的用户关联的
RelyingPartyRegistration
创建、签名和序列化<saml2:LogoutResponse>
-
根据
RelyingPartyRegistration
向断言方发送重定向或发布
添加将注销功能添加到服务提供商。
因为它是一项可选功能,所以您需要为每个人启用它。
您可以通过设置属性来执行此操作。saml2Logout RelyingPartyRegistration RelyingPartyRegistration.Builder#singleLogoutServiceLocation |
1 | - 首先,将您的签名密钥添加到实例或多个实例RelyingPartyRegistration |
2 | - 其次,指示您的应用程序希望使用 SAML SLO 注销最终用户 |
添加将注销功能添加到服务提供商。
因为它是一项可选功能,所以您需要为每个人启用它。
您可以通过设置属性来执行此操作。saml2Logout RelyingPartyRegistration RelyingPartyRegistration.Builder#singleLogoutServiceLocation |
配置注销终结点
有三种行为可以由不同的终结点触发:
-
RP 发起的注销,允许经过身份验证的用户通过向断言方发送
POST
<saml2:LogoutRequest>
-
AP 发起的注销,允许断言方向应用程序发送
<saml2:LogoutRequest>
-
AP 注销响应,允许断言方发送响应 RP 发起的
<saml2:LogoutResponse>
<saml2:LogoutRequest>
第一种是通过在主体类型为 时执行正常触发的。POST /logout
Saml2AuthenticatedPrincipal
第二个是通过 POST 到端点触发的,并由断言方签名。/logout/saml2/slo
SAMLRequest
第三个是通过 POST 到终端节点触发的,并由断言方签名。/logout/saml2/slo
SAMLResponse
由于用户已登录或原始注销请求已知,因此已知道。
因此,默认情况下不是这些 URL 的一部分。registrationId
{registrationId}
此 URL 可在 DSL 中自定义。
例如,如果要将现有信赖方迁移到 Spring Security,则断言方可能已经指向 .
若要减少断言方的配置更改,可以在 DSL 中配置筛选器,如下所示:GET /SLOService.saml2
-
Java
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2"))
);
您还应该在 .RelyingPartyRegistration
自定义分辨率<saml2:LogoutRequest>
通常需要在 Spring Security 提供的默认值之外设置其他值。<saml2:LogoutRequest>
默认情况下,Spring Security 将发出 a 并提供:<saml2:LogoutRequest>
-
属性 - from
Destination
RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceLocation
-
属性 - GUID
ID
-
元素 - 从
<Issuer>
RelyingPartyRegistration#getEntityId
-
元素 - 从
<NameID>
Authentication#getName
若要添加其他值,可以使用委派,如下所示:
@Bean
Saml2LogoutRequestResolver logoutRequestResolver(RelyingPartyRegistrationResolver registrationResolver) {
OpenSaml4LogoutRequestResolver logoutRequestResolver
new OpenSaml4LogoutRequestResolver(registrationResolver);
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>
通常需要在 Spring Security 提供的默认值之外设置其他值。<saml2:LogoutResponse>
默认情况下,Spring Security 将发出 a 并提供:<saml2:LogoutResponse>
-
属性 - from
Destination
RelyingPartyRegistration#getAssertingPartyDetails#getSingleLogoutServiceResponseLocation
-
属性 - GUID
ID
-
元素 - 从
<Issuer>
RelyingPartyRegistration#getEntityId
-
元素 -
<Status>
SUCCESS
若要添加其他值,可以使用委派,如下所示:
@Bean
public Saml2LogoutResponseResolver logoutResponseResolver(RelyingPartyRegistrationResolver registrationResolver) {
OpenSaml4LogoutResponseResolver logoutRequestResolver =
new OpenSaml3LogoutResponseResolver(relyingPartyRegistrationResolver);
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>
RelayState
InResponseTo
<saml2:LogoutResponse>
如果要将注销请求存储在会话以外的其他位置,可以在 DSL 中提供自定义实现,如下所示:
http
.saml2Logout((saml2) -> saml2
.logoutRequest((request) -> request
.logoutRequestRepository(myCustomLogoutRequestRepository)
)
);