摘要:
本文将针对 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
对象的操作进行了有效的封装。