Spring SAML - 支持自定义SAML断言

时间:2016-07-05 07:53:26

标签: spring spring-security spring-saml

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

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

问题

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

也可能会有更多更改,例如不同的绑定等......

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

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

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

1 个答案:

答案 0 :(得分:2)

  

我的问题是支持此类问题的最佳选择/最佳做法是什么   方案,并能够扩展我的SP以支持更多的SAML流程   Assertion属性和更多配置的差异?

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

  

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

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

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

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

我受限于允许发布的代码,但我已经包含了我的内容。

  • 入口点网址 - 如果您的配置中有多个带有别名的IDP,默认情况下,入口点网址将为

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

    如果您位于负载均衡器后面,则可以将其配置为将所需的任何URL重写为客户的URL。

  • 绑定和断言 - 这些在每个Service Providers 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消息)。