我们的应用程序使用 spring-security 4.2.1 和 spring-security-saml2-core 1.10.0 实现 SAML 单点登录。 SLO 无法正常工作。当在不同的应用程序中注销时,发送给我们的 SLO 请求不会终止本地会话。 当 IdP 存储并包含在 SAML 注销请求中的 JSESSIONID cookie 被浏览器阻止时,似乎会发生这种情况。因此,Spring 无法找到给定会话的安全上下文,因此无法终止会话并返回错误消息“没有用户登录”。
在处理注销请求之前,
SecurityContextPersistenceFilter
会创建一个新上下文,因为会话 ID 丢失。
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
接下来,
SAMLLogoutProcessingFilter.processLogout()
尝试获取安全上下文身份验证对象,该对象为空。
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SAMLCredential credential = null;
if (auth != null) {
credential = (SAMLCredential) auth.getCredentials();
}
由于凭证为空,
SingleLogoutProfileImpl.processLogoutRequest()
会抛出异常。
// Check whether any user is logged in
if (credential == null) {
throw new SAMLStatusException(StatusCode.UNKNOWN_PRINCIPAL_URI, "No user is logged in");
}
我尝试的第一个修复是设置 cookie 的
SameSite
属性。之前没有设置过,因此浏览器默认为 SameSite=Lax
并在此基础上阻止 cookie。设置 SameSite=None
可以解决问题,但仅 如果浏览器设置为 允许 第三方 cookie。
我尝试的下一件事是
Partitioned
cookie 属性。然而,当发送注销请求时,浏览器也会阻止该 cookie,因为 cookie 被分区到 IdP 的域而不是应用程序的域。另外,截至目前,并非所有浏览器都支持分区 cookie。
也许 Spring 可以配置/实现为基于注销请求中包含的 SAML 消息找到正确的会话以进行单点注销处理。目前我们的应用程序使用
HttpSessionSecurityContextRepository
实现来查找安全上下文。
我对此提出了自己的解决方案。不幸的是,我不被允许分享代码,所以出于谨慎考虑,我不会这样做。我会尽力解释没有它的解决方案。
我编写了一个扩展
SecurityContextRepository
的类,以将安全上下文对象存储在映射中,其中密钥是 SAML 消息中的 SessionIndex
属性。该类将所有其他正常的安全上下文处理委托给我们之前使用过的HttpSessionSecurityContextRepository
。
在方法
loadContext
中,我检查请求是否包含 LogoutRequest
SAML 消息。如果没有,我会调用委托存储库的 loadContext
。否则,解析的 SAML 消息可以转换为 LogoutRequest
对象,并且可以提取会话索引,我可以检查安全上下文映射是否返回具有该会话索引的安全上下文。
请注意,即使我返回在地图中找到的安全上下文,我仍然需要调用委托的
loadContext
方法,因为它也会进行请求/响应包装,如果不这样做,则会导致异常。
在
saveContext
方法中,我从 SAMLCredential
对象获取 Authentication
,这是从传递给该方法的 context
获得的。会话索引可以从 SAMLCredential
对象中提取,上下文可以放入映射中。在方法的最后,我总是调用委托的 saveContext
方法。
在此解决方案中,我选择在调用
loadContext
时手动解析 SAML 消息。我不确定是否可能或实际以某种方式传递现有的 SAML 处理器 bean 并使用它。这种 SAML 解析方法对我有用:http://sureshatt.blogspot.com/2012/11/how-to-read-saml-20-response-with.html
另请注意,如果您手动解析 SAML 消息:如果使用 HTTP 重定向绑定(即,如果注销请求是 GET 请求),则需要增加 SAML 有效负载。我为此使用了 Java
Inflater
对象:https://docs.oracle.com/javase/8/docs/api/java/util/zip/Inflater.html
如果您对此有任何疑问,请随时询问!