Spring SAML - 支持自定义 SAML 断言

Posted

技术标签:

【中文标题】Spring SAML - 支持自定义 SAML 断言【英文标题】:Spring SAML - support for customized SAML Assertion 【发布时间】:2016-11-06 23:20:37 【问题描述】:

我们有一个产品有一个客户,当我们充当服务提供商并且 idp 在客户端时,我们使用 Spring Security SAML 为该客户实施了 SAML 流。

现在我们有另一个客户也希望使用 SAML 进行身份验证,并且我们希望同一个 SP 为该客户实施 SAML 流,第二个客户将有 2 个 SAML 流,一个用于移动设备,一个用于其他设备使用相同 IDP 的设备。两个客户的 IDP 不同。

问题

这两个客户之间存在一些差异,例如断言属性不同,成功认证的动作不同,目前我们提供自己的实现。

还可能会有更多的变化,比如不同的绑定等......

我的问题是什么是支持这种情况并能够扩展我的 SP 以支持更多 SAML 流的最佳选择/最佳实践,但断言属性和更多配置存在差异?

当我们使用 Spring SAML 时,我们应该为每种 SAML 风格使用不同的 Spring Security 上下文文件吗?

并行使用多个上下文时是否存在线程安全问题?

【问题讨论】:

【参考方案1】:

我的问题是支持此类的最佳选择/最佳实践是什么 场景并能够扩展我的 SP 以支持更多 SAML 流 Assertion 属性和更多配置的差异?

要分支某些配置,例如断言属性,您需要创建单独的服务提供者。可以共享其他配置和服务。其他配置应该共享。例如,我使用单个自定义 SAMLUserDetailsS​​ervice 实现,它从凭证中提取唯一的 EntityID,并使用它为每个 IDP 映射不同的 SAML 属性。

当我们使用 Spring SAML 时,我们应该使用不同的 Spring Security 每个 SAML 风格的上下文文件? 在中使用多个上下文时是否存在线程安全问题 并行?

我不建议单独运行多个安全上下文。根据我的经验,Spring SAML 中涉及很多配置,并且很有可能,您将不得不通过这种方式不必要地复制大量代码。

在 Spring SAML 中,存在为不同的服务提供者使用不同别名的概念。我已经为许多 IDP 设置了许多服务提供者,并且能够使用一个 Spring Security 上下文并在需要处理差异的地方实现自定义服务。我没有您的要求的完整列表,并且可能有一些根本无法在单个 Spring 安全上下文中完成,但我会等到确保情况如此,然后再采取该路线。

每个 IDP 之间具体需要有哪些不同?

我可以发布的代码是有限的,但我已经包含了我可以发布的代码。

入口点 URL - 如果您有多个 IDP 在配置中设置了别名,则入口点 URL 默认为

"/saml/login/alias/" +productAlias+ "?idp=" + entityId;

如果您在负载均衡器后面,您可以将其配置为将您想要的任何 URL 重写为客户的 URL。

绑定和断言 - 这些在每个服务提供者 metadata.xml 文件中进行配置,并且对于每个客户可能不同。真正的挑战是如何从经过身份验证的 SAML 请求中提取属性并以可用的形式获取它。

我不知道是否有更好的方法来做到这一点,但我的要求是为我配置的任何 IDP 提供任何可映射和可配置的绑定。为此,我实现了一个自定义SAMLUserDetailsService。从传入服务的SAMLCredential 中,您可以使用credential.getRemoteEntityID() 为客户提取映射。从那里您需要从凭证中解析出属性。

为 Microsoft 和其他 IDP 解析 SAML 属性的示例

 public class AttributeMapperImpl implements AttributeMapper 

    @Override
    public Map<String, List<String>> parseSamlStatements(List<AttributeStatement> attributeList) 
        Map<String, List<String>> map = new HashMap<>();
        attributeList.stream().map((statement) -> parseSamlAttributes(statement.getAttributes())).forEach((list) -> 
            map.putAll(list);
        );
        return map;
    

    @Override
    public Map<String, List<String>> parseSamlAttributes(List<Attribute> attributes) 
        Map<String, List<String>> map = new HashMap<>();
        attributes.stream().forEach((attribute) -> 
            List<String> sList = parseXMLObject(attribute.getAttributeValues());
            map.put(attribute.getName(), sList);
        );
        return map;
    

    @Override
    public List<String> parseXMLObject(List<XMLObject> objs) 
        List<String> list = new ArrayList<>();

        objs.stream().forEach((obj) -> 
            if(obj instanceof org.opensaml.xml.schema.impl.XSStringImpl)
                XSStringImpl xs = (XSStringImpl) obj;
                list.add(xs.getValue());
            else if(obj instanceof org.opensaml.xml.schema.impl.XSAnyImpl)
                XSAnyImpl xs = (XSAnyImpl) obj;
                list.add(xs.getTextContent());
            
        );  

        return list;
    

    @Override
    public String parseSamlStatementsToString(Map<String, List<String>> map) 
        String values = "";
        Iterator it = map.entrySet().iterator();
        while (it.hasNext()) 
            Map.Entry pair = (Map.Entry) it.next();
            values += pair.getKey() + "=" + pair.getValue() + " ";
            it.remove(); // avoids a ConcurrentModificationException
        
        return values;
    


对成功/失败采取的行动 - 有许多可能的方法来做到这一点。我选择在控制器中使用单个端点,该端点可以访问所有请求成功后进入的会话。身份验证成功后,我可以退出会话,用户来自哪个 IDP,并相应地重定向它们。失败有点困难,因为完全有可能,而且某些失败可能会非常严重,以至于您不知道请求来自哪个 IDP(即,如果 saml 消息使用错误的证书签名)。

【讨论】:

idp 之间的区别是入口点 url、绑定、成功和失败的操作以及断言......你能分享一个演示它的示例代码吗?

以上是关于Spring SAML - 支持自定义 SAML 断言的主要内容,如果未能解决你的问题,请参考以下文章

Spring SAML - 如何在 SP HTTP 请求上添加自定义字段?

打开和关闭 Spring Security SAML 的方法

Spring Boot/Angular整合Keycloak实现单点登录

使用 Spring Boot 拦截 SAML Http 请求

Spring SAML:更新元数据提供程序不会更新使用的签名证书

Spring SAML 示例:不支持 SSOCircle SHA256