摘要:
本文将针对 OpenSaml V3 的新特性进行源码分析。由于 OpenSAML 最新版刚 release 不久,所以资料甚少,最好的学习方法就是阅读源码。作为一个优秀的开源 SAML 库,其代码和测试都具有较高的参考性。能够认真分析一下其源码,受益匪浅。
源码获取
本文的源码均参考官方的 git 仓库,当然也可以使用我的 github 镜像,会不定期更新,合并官方更新,并增加一些自己的笔记和分类图。
OpenSaml v3 新特性概述
目前 opensaml v3 的官方文档很少,只是简略的介绍了一些其设计思想。目前最大的设计思路的变化(和第二版相比)主要有三点
- 消息相关的组件
- Metadata Providers
- 安全配置组件
个人认为其中的消息的重构是新版 opensaml 最为重要的变化,这也将彻底改变对于 opensaml 这个库的使用思路。
旧版的消息上下文是一个静态的结构,新版的消息是一个动态可扩展的消息上下文。同时所有的操作都会产生或者处理消息的状态。
之前版本的消息机制有很多令人费解的概念,如 messag issuer, peer entity 和 relying party。现在这些概念将全部被清楚。
另外这里还引入一个讨论,就是那些内容需要在 opensaml 中实现,那些应该归 IdP 来实现。Chad(idp 项目负责人)认为消息处理应该在 idp 做。但是Putman 认为认真思考这个问题,因为目前的 idp 使用了 WebFlow 来处理消息,而 opensaml 致力于创建一套独立于框架的机制。这点个人来讲十分赞同的,因为并不是所有人都用 Spring 框架。
最后在第三版将会彻底抛弃 org.opensaml.ws.message.MessageContext 这个类。
新的消息机制的目的
General goals:
Support non-XML messaging Existing API assumes all messages are XMLObjects. Fails for OpenID, and other non-XML cases.
支持非 XML 消息
虽然是 opensaml,其消息应该是 XML,但是这里已经将消息抽象为更加通用形式,看来 opensaml 的目标是兼容更多的协议。
扩展的状态存储
解决当前用于以可扩展方式存储状态的上下文的复杂嵌套层次结构。 由于可扩展性的多个维度,这是令人困惑和笨重的。
重用代码
尽可能的重用代码,本文介绍的 ProfileAction 就是重用代码的一个重要方法。
传输的抽象
之前的输入和输出的抽象同样费解,新版进行改进。
消息机制的参考
避免重复造轮子,所以新版的 opensaml 参考了如下开源库:
Apache Axis2; Apache CXF; JAX-WS; Apache Struts 2/WebWork and the Servlet API
Messaging Context

1 | public final class ProfileRequestContext<InboundMessageType, OutboundMessageType> extends |
ProfileAction

我们这里简要介绍一下 ProfileAction 的机制,由于直接操纵 SAML 对象很容易出错,而且代码比容易维护,并且当有多种 profile 的时候,代码重复率很高。所以 Opensaml 提出一个概念,将某种针对 SAML 对象的操纵封装成一种 Action,然后这个 Action 可以重用。
Opensaml v3 已经创建了若干 Action,比如
AddStatusResponseShell 创建一个空的 SAML response,其中包含状态。
AddStatusToResponse 向 SAML response 加入 Status。
Guava Function
1 | private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy; |
这个 Function 应该就是通过输入的 context,返回一个 IdentifierGenerationStrategy。
1 | Function<A, B> |
输入 A,返回 B。
1 | Function<ProfileRequestContext,String> issuerLookupStrategy; |
案例解析
我们以 AddStatusToResponse 为例进行简要分析。
其中 包含有多个 strategy, 这些 strategy 就是用来执行该 action 所准备的数据,比如responseLookupStrategy 表示该 Action 需要执行的对象是如何获取到的。另外我们还可以看到其构造函数中指定了默认的策略,就是返回一个 OutboundMessageContextLookup 也就是获取 ProfileRequestContext 的 outbountMessageContext。我们之前提过,在 action 的执行过程中,大部分都是使用 ProfileRequestContext 来存储各种状态,包括原始数据和最终处理结果。所以我们在使用这个 Action 的时候,如果没有指定responseLookupStrategy,那么就需要在传入该 Action 的 ProfileRequestContext的 outbound 的消息需要是 Saml Response 类型的。
对于 Action 的执行包含三个阶段,
- doPreExecute
- doExecute
- doPostExecute
这三个方法是通过抽象类的execute方法来进行执行。
1 | if (doPreExecute(profileRequestContext)) { |
pre 阶段一般是通过 strategy 来准备出 action 需要操作的数据,比如这里获取到 response: response = responseLookupStrategy.apply(profileRequestContext);
do 阶段最为关键,往往是操作 SAML Object 的核心阶段,通过现有的数据进行更新添加等动作等到目标的数据。如果逻辑复杂还可以创建一些其他的函数来辅助执行动作,比如buildStatusCode,buildStatusMessage等
post 是在执行完 doExecute 之后进行的操作。
这样这个 Action 实现后将可以重用,而且想 Shibboleth IdP v3 项目正是通过一个 Flow 来集合一个 action 的序列来完成某些 profile,这样让代码等到了高度重用,而且将复杂的 SAML 对象的操作进行了有效的封装。