Opensaml-v3源码解析之ProfileAction

摘要:

本文将针对 OpenSaml V3 的新特性进行源码分析。由于 OpenSAML 最新版刚 release 不久,所以资料甚少,最好的学习方法就是阅读源码。作为一个优秀的开源 SAML 库,其代码和测试都具有较高的参考性。能够认真分析一下其源码,受益匪浅。

源码获取

本文的源码均参考官方的 git 仓库,当然也可以使用我的 github 镜像,会不定期更新,合并官方更新,并增加一些自己的笔记和分类图。

OpenSaml v3 新特性概述

目前 opensaml v3 的官方文档很少,只是简略的介绍了一些其设计思想。目前最大的设计思路的变化(和第二版相比)主要有三点

  1. 消息相关的组件
  2. Metadata Providers
  3. 安全配置组件

个人认为其中的消息的重构是新版 opensaml 最为重要的变化,这也将彻底改变对于 opensaml 这个库的使用思路。

旧版的消息上下文是一个静态的结构,新版的消息是一个动态可扩展的消息上下文。同时所有的操作都会产生或者处理消息的状态。

之前版本的消息机制有很多令人费解的概念,如 messag issuerpeer entityrelying 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

enter description here

1
2
public final class ProfileRequestContext<InboundMessageType, OutboundMessageType> extends
InOutOperationContext<InboundMessageType, OutboundMessageType>{}

ProfileAction

enter description here

我们这里简要介绍一下 ProfileAction 的机制,由于直接操纵 SAML 对象很容易出错,而且代码比容易维护,并且当有多种 profile 的时候,代码重复率很高。所以 Opensaml 提出一个概念,将某种针对 SAML 对象的操纵封装成一种 Action,然后这个 Action 可以重用。

Opensaml v3 已经创建了若干 Action,比如

AddStatusResponseShell 创建一个空的 SAML response,其中包含状态。

AddStatusToResponseSAML response 加入 Status

Guava Function

1
2
3
4
5
6
7
private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;

idGeneratorLookupStrategy = new Function<ProfileRequestContext,IdentifierGenerationStrategy>() {
public IdentifierGenerationStrategy apply(ProfileRequestContext input) {
return new SecureRandomIdentifierGenerationStrategy();
}
};

这个 Function 应该就是通过输入的 context,返回一个 IdentifierGenerationStrategy

1
Function<A, B>

输入 A,返回 B。

1
Function<ProfileRequestContext,String> issuerLookupStrategy;

案例解析

我们以 AddStatusToResponse 为例进行简要分析。

其中 包含有多个 strategy, 这些 strategy 就是用来执行该 action 所准备的数据,比如responseLookupStrategy 表示该 Action 需要执行的对象是如何获取到的。另外我们还可以看到其构造函数中指定了默认的策略,就是返回一个 OutboundMessageContextLookup 也就是获取 ProfileRequestContextoutbountMessageContext。我们之前提过,在 action 的执行过程中,大部分都是使用 ProfileRequestContext 来存储各种状态,包括原始数据和最终处理结果。所以我们在使用这个 Action 的时候,如果没有指定responseLookupStrategy,那么就需要在传入该 ActionProfileRequestContextoutbound 的消息需要是 Saml Response 类型的。

对于 Action 的执行包含三个阶段,

  1. doPreExecute
  2. doExecute
  3. doPostExecute

这三个方法是通过抽象类的execute方法来进行执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (doPreExecute(profileRequestContext)) {
try {
doExecute(profileRequestContext);
} catch (final Throwable t) {
try {
if (t instanceof Exception) {
doPostExecute(profileRequestContext, (Exception) t);
} else {
doPostExecute(profileRequestContext);
}
}
}

doPostExecute(profileRequestContext);
}

pre 阶段一般是通过 strategy 来准备出 action 需要操作的数据,比如这里获取到 responseresponse = responseLookupStrategy.apply(profileRequestContext);

do 阶段最为关键,往往是操作 SAML Object 的核心阶段,通过现有的数据进行更新添加等动作等到目标的数据。如果逻辑复杂还可以创建一些其他的函数来辅助执行动作,比如buildStatusCodebuildStatusMessage

post 是在执行完 doExecute 之后进行的操作。

这样这个 Action 实现后将可以重用,而且想 Shibboleth IdP v3 项目正是通过一个 Flow 来集合一个 action 的序列来完成某些 profile,这样让代码等到了高度重用,而且将复杂的 SAML 对象的操作进行了有效的封装。