Spring SAML – 支持自定义SAML断言

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

现在我们有另一个客户也希望身份验证与SAML一起使用,我们希望相同的SP为该客户实施SAML流程,第二个客户将有2个SAML流量用于移动设备,另一个用于其他设备使用相同的IDP.两个客户的国内流离失所者是不同的.

问题

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

也可能有更多变化,如不同的绑定等…

我的问题是什么是支持这种情况的最佳选择/最佳实践,并且能够扩展我的SP以支持更多SAML流,并且Assertion属性和更多配置有所不同?

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

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

最佳答案

My question is what is the best option/best practice to support such
scenario and to be able to extend my SP to support more SAML flows
with differences in the Assertion attributes and more configurations?

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

When we use Spring SAML should we use different Spring Security
context files for each of the SAML flavors?
Are there issues with thread safety when using multiple contexts in
parallel?

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

在Spring SAML中,存在为不同的服务提供商使用不同别名的概念.我为许多IDP设置了许多服务提供商,并且能够使用一个Spring Security上下文并实现我需要处理差异的自定义服务.我没有您的要求的完整列表,可能有一些根本无法在单个弹簧安全上下文中完成,但我会等到确保在采用该路线之前就是这种情况.

每个IDP之间需要具体的不同之处是什么?

我被限制在允许发布的代码中,但我已经包含了我能做的内容.

>入口点URL – 如果您的配置中有多个带有别名的IDP,默认情况下,入口点URL将是

“/ saml / login / alias /”productAlias“?idp =”entityId;

如果您位于负载均衡器后面,则可以将其配置为将所需的任何URL重写为客户的URL.
>绑定和断言 – 这些在每个Service Providers metadata.xml文件中配置,并且对于每个客户可以是不同的.真正的挑战是如何从经过身份验证的SAML请求中提取属性并以可用的形式获取它.

我不知道是否有更好的方法可以做到这一点,但我的要求是为我配置的任何IDP配置可映射的任何绑定.为此,我实现了一个自定义SAMLUserDetailsS​​ervice.从传入服务的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消息).

点赞