Apache Neethi 3.2.2 WS-Policy 配置详解

详解 Apache Neethi 3.2.2 下 WS-Policy 文档的元素语法、Optional/Ignorable 语义、合并与规范化算法及生产策略模板,全部示例实测验证。

Apache NeethiNeethiApacheJava配置详解2026/10/15

Apache Neethi 3.2.2 WS-Policy 配置详解

目标:掌握 WS-Policy 文档在 Neethi 3.2.2 中的配置写法与组合语义(Optional/Ignorable、ExactlyOne/All 嵌套、PolicyReference),并能用 merge/normalize/intersect 实测验证效果。已在 Ubuntu 22.04(OpenJDK 17)实测通过。

一、环境信息

项目值
操作系统Ubuntu 22.04.5 LTS
系统架构x86_64(amd64)
JDKOpenJDK 17.0.20.1
组件版本Apache Neethi 3.2.2、axiom-api/axiom-impl 1.3.0
实验项目/opt/neethi-demo
策略文件src/main/resources/policy1.xml、policy2.xml、policy3.xml

表后逐项说明:本文的"配置"即 WS-Policy XML 文档本身与其处理参数(merge/normalize/intersect 的调用方式);三份实测策略分别覆盖安全组合、QoS 可选断言、嵌套策略三个场景。

二、配置体系概览

2.1 WS-Policy 文档结构

<wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"
            xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
            wsu:Id="P1">
  <wsp:ExactlyOne>
    <wsp:All>
      <断言元素/>
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

逐项说明:根元素 wsp:Policy 固定命名空间 http://www.w3.org/ns/ws-policy;wsu:Id 是策略标识(Neethi 解析为 getId());wsp:ExactlyOne 与 wsp:All 是组合算子;中间的业务断言元素使用自定义命名空间(如 WS-SecurityPolicy 的 sp:)。

2.2 关键属性与引用

属性/元素位置语义Neethi 模型
wsp:Optional="truefalse"断言元素断言可选(可要可不要)
wsp:Ignorable="truefalse"断言元素不理解时可忽略
wsu:Id根元素策略 IDPolicy.getId()
Name 属性根元素策略名Policy.getName()/setName()
wsp:PolicyReference URI任意位置引用其他策略PolicyReference
子级 wsp:Policy断言元素内嵌套策略PolicyContainingAssertion

表后逐项说明:Optional 与 Ignorable 是 WS-PolicyAttachment 定义的属性;嵌套 wsp:Policy 是 WS-SecurityPolicy 类规范的标准组织方式(断言的参数化);PolicyReference 的解析需要配合 PolicyRegistry 提供目标策略。

💡 技术拓展:断言匹配算法有两个方向——merge(合并)产生"同时满足两策略"的方案集,量化为 alternatives 笛卡尔积;intersect(交集)产生"双方共同接受"的方案集,需要断言实现 IntersectableAssertion 才能参与细粒度协商。本文以 merge + normalize 实测为主线(2×4=8 方案可完全复现),intersect 仅在策略协商(服务端能力声明 × 客户端需求)场景使用。

三、策略文件的查看与修改方式

3.1 查看

策略文件是普通 XML,直接 cat/grep 查看;解析验证用实验工程的主类:

cd /opt/neethi-demo
grep -n "Optional" src/main/resources/policy2.xml

实测回显:

7:      <qos:Compression wsp:Optional="true"/>
8:      <qos:Auditing wsp:Optional="true"/>

3.2 修改与验证

修改策略后无需重启任何服务(Neethi 是库不是进程),重新运行解析程序即生效。标准验证循环:

LC_ALL=C.UTF-8 java -Xms256m -Xmx256m -cp "target/classes:$(cat /tmp/cp.txt)" com.example.PolicyParseDemo

实测回显(policy1.xml,输出节选):

policy id  = P1
---- alternative 1 ----
assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportBinding optional=false ignorable=false text=

说明:改动 Optional/Ignorable 后回显里的 optional/ignorable 布尔值立即变化;改动组合结构后 alternatives 计数变化。

四、核心配置详解

4.1 组合算子:ExactlyOne 与 All

语义规则

  • ExactlyOne:从子项中"任选其一"。
  • All:子项"全部成立"。
  • Policy 根内部等价于一个 ExactlyOne 节点。

嵌套规则与展开

  • 正则形(normal form)= ExactlyOne( All, All, ... ):每个 All 是一条完整 alternative。
  • 任意嵌套组合都能通过 normalize(true) 展开为正则形。

实测:policy1.xml 的 ExactlyOne 下两条 All → 2 个 alternative:

total alternatives = 2

4.2 wsp:Optional 语义与展开实测

optional 断言在解析后 isOptional()=true,规范化时展开为"有/无"两个分支。policy2.xml 含两个 optional 断言(Compression、Auditing)与一个必选断言(ReliableMessaging):

<wsp:ExactlyOne>
  <wsp:All>
    <qos:ReliableMessaging/>
    <qos:Compression wsp:Optional="true"/>
    <qos:Auditing wsp:Optional="true"/>
  </wsp:All>
</wsp:ExactlyOne>

实测展开结果(normalize(true) 后 4 条 alternative,节选自序列化输出):

<wsp:All>...<qos:ReliableMessaging></qos:ReliableMessaging><qos:Compression></qos:Compression><qos:Auditing></qos:Auditing></wsp:All>
<wsp:All>...<qos:ReliableMessaging></qos:ReliableMessaging><qos:Compression></qos:Compression></wsp:All>
<wsp:All>...<qos:ReliableMessaging></qos:ReliableMessaging><qos:Auditing></qos:Auditing></wsp:All>
<wsp:All>...<qos:ReliableMessaging></qos:ReliableMessaging></wsp:All>

逐项说明:2 个可选断言产生 2×2=4 种组合,必选断言在每条都出现——这正是"optional 即多种方案"的规范语义;序列化时 Optional 属性已被展开消除,输出中不再出现 wsp:Optional。

4.3 wsp:Ignorable 语义

  • ignorable=true 声明"不理解此断言的方可以忽略它而不宣布协商失败"。
  • Neethi 解析后存为 isIgnorable() 标志,merge/intersect 时按规范参与匹配。
  • 实测验证:policy1.xml 中断言均未设置 Ignorable,解析回显 ignorable=false;在断言上加 wsp:Ignorable="true" 后重新解析即可看到 true。

4.4 嵌套策略(断言参数化)

WS-SecurityPolicy 风格的嵌套写法(policy3.xml):

<sp:TransportBinding>
  <wsp:Policy>
    <sp:TransportToken>
      <wsp:Policy>
        <sp:HttpsToken/>
      </wsp:Policy>
    </sp:TransportToken>
    <sp:IncludeTimestamp/>
  </wsp:Policy>
</sp:TransportBinding>

实测解析模型:

top assertion: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportBinding class=PolicyContainingPrimitiveAssertion
  nested: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportToken class=PolicyContainingPrimitiveAssertion
  nested: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}IncludeTimestamp class=PrimitiveAssertion

逐项说明:有子策略的断言 → PolicyContainingPrimitiveAssertion(getPolicy() 下钻);无子策略 → PrimitiveAssertion;normalize(true) 会把嵌套策略一并展开。

4.5 PolicyReference 与注册表联动

引用写法:

<wsp:PolicyReference URI="#P1"/>

处理模式:先解析被引用策略并放入 PolicyRegistry,再解析含引用的文档,把 PolicyReference 的 URI 映射到 registry.lookup。registry 的行为实测(PolicyRegistryDemo):

lookup service-security -> id = P1
lookup not-exist -> null
after remove, lookup service-qos -> null

逐项说明:lookup 未命中返回 null(引用解析代码必须判空);register 同名覆盖;无键枚举接口,引用清单需使用方维护。

4.6 合并与规范化算法实测

merge 语义:两个策略合并后,方案集为双方 alternatives 的组合(笛卡尔积),再经 normalize 展开可选断言。

实测数据(policy1.xml 2 条方案 × policy2.xml 1 条方案含 2 个 optional):

p1 alternatives = 2, p2 alternatives = 1
merged alternatives (not normalized) = 2
normalized alternatives = 8

逐项说明:2 ×(1 × 2×2)= 8;未规范化时 P2 仍作为嵌套结构挂在每条 P1 方案下,所以计数是 2;规范化后每条 alternative 都是"一条 All 内平铺全部断言",第一条为(TransportBinding+IncludeTimestamp+SignedSupportingTokens+ReliableMessaging+Compression+Auditing),完整序列化输出含 8 个 wsp:All(见集成实战篇 4.3 节)。

4.7 序列化输出控制

XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(sw);
writer.writeStartDocument("UTF-8", "1.0");
writer.writeCharacters("\n");
normalized.serialize(writer);
writer.writeEndDocument();

实测输出(节选):

<?xml version='1.0' encoding='UTF-8'?>
<wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"><wsp:ExactlyOne><wsp:All>...

逐项说明:serialize 不写 XML 声明,需要自行 writeStartDocument;输出紧凑无缩进,命名空间在首次用到处声明;写回的文档可直接被 PolicyEngine.getPolicy 再次解析(往返一致)。

五、生产策略模板

服务端安全与 QoS 声明模板(两种安全方案 + 可选 QoS,可直接作为服务能力声明发布):

<wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy"
            xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy"
            xmlns:qos="http://example.org/qos"
            xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
            wsu:Id="service-capability">
  <wsp:ExactlyOne>
    <wsp:All>
      <sp:TransportBinding>
        <wsp:Policy>
          <sp:TransportToken>
            <wsp:Policy>
              <sp:HttpsToken/>
            </wsp:Policy>
          </sp:TransportToken>
          <sp:IncludeTimestamp/>
        </wsp:Policy>
      </sp:TransportBinding>
      <qos:ReliableMessaging/>
      <qos:Compression wsp:Optional="true"/>
      <qos:Auditing wsp:Optional="true"/>
    </wsp:All>
    <wsp:All>
      <sp:AsymmetricBinding/>
      <sp:EncryptedParts/>
      <qos:ReliableMessaging/>
    </wsp:All>
  </wsp:ExactlyOne>
</wsp:Policy>

逐项说明:方案一走 HTTPS 传输绑定并带两个可选 QoS;方案二走非对称绑定(更重的端到端安全)不带可选 QoS;ReliableMessaging 两方案都必选;业务断言 qos: 命名空间由自定义构建器解析(集成实战篇第 6 节)。

服务端与客户端协商(交集)模板:

Policy server = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy1.xml"));
Policy client = PolicyEngine.getPolicy(new FileInputStream("src/main/resources/policy2.xml"));
Policy agreed = server.intersect(client);
Policy normal = agreed.normalize(true);

说明:intersect 挑出双方都接受的 alternative;未实现 IntersectableAssertion 的断言按 QName 相等性参与匹配。生产建议对协商结果判空(isEmpty())并输出日志,避免发布"无共同方案"的服务。

六、常见问题(FAQ)

Q1:wsp:Optional 写在哪个命名空间? A:属性本身属于 WS-Policy 命名空间(http://www.w3.org/ns/ws-policy),写在断言元素上,如 qos:Compression wsp:Optional="true"。漏写 wsp: 前缀时 Neethi 不识别该属性(实测 isOptional() 返回 false)。

Q2:Optional 和 Ignorable 有什么区别? A:Optional 表示"这个断言描述的能力可以有也可以没有",规范化时展开为有/无两个方案;Ignorable 表示"你不认识它也没关系,忽略即可",不产生方案分裂。实测 policy2.xml 两个 Optional 断言把 1 条方案展开成 4 条,而 Ignorable 不会增加 alternatives。

Q3:merge 结果的 alternatives 数量怎么计算? A:规范化后 = 策略 A 方案数 × 策略 B 方案数 × 各自 optional 断言的 2 的幂次展开。实测 2 ×(1×2×2)= 8。未 normalize 的 merge 结果包含嵌套结构,直接计数会偏小(实测显示 2)。

Q4:为什么 normalize 之后断言的 wsp:Optional 属性消失了? A:展开已经把"可选"语义物化为多条 alternative,序列化输出的是正则形,Optional 属性不再需要。重新用 PolicyEngine.getPolicy 解析该输出,isOptional() 全为 false(语义已由结构表达)。

Q5:PolicyReference 的 URI 支持跨文件吗? A:URI 格式自定(#P1 片段式或完整 URL 均可),Neethi 不自动加载目标——必须由调用方解析目标策略、放入 PolicyRegistry,再按 URI lookup 完成引用绑定(实测 registry.register/lookup 行为)。

Q6:断言元素需要注册构建器才能解析吗? A:不需要。未知命名空间断言由内建 XMLPrimitiveAssertionBuilder 兜底解析为 PrimitiveAssertion(保留 QName/属性/文本)。只有需要把 XML 结构转成自定义 Java 对象(如 qos:ReliableMessaging 映射为枚举)时才注册自定义构建器(实测 [QosAssertionBuilder] 回调)。

Q7:intersect 返回空策略说明什么? A:双方没有任何共同接受的方案。此时 agreed.isEmpty() 为 true 或 alternatives 为 0,服务端应拒绝绑定并输出双方策略诊断;不要拿空策略继续发布。

Q8:策略文件里的注释会解析吗? A:XML 注释被解析器跳过,不影响模型。但注意注释不能包裹被引用的断言(引用按 QName 找元素),否则构建器收不到。

Q9:一条 alternative 里的断言顺序有意义吗? A:All 语义下顺序不影响"是否满足",但影响序列化输出与日志可读性。规范形输出顺序与文档顺序一致(实测:TransportBinding、IncludeTimestamp、SignedSupportingTokens 依原序)。

Q10:如何校验一份策略文档是否合法? A:三步:PolicyEngine.getPolicy 能否解析(抛 IllegalArgumentException 即结构错误);getAlternatives 计数是否符合组合语义(手算交叉积对照);normalize(true) 后 serialize 再回读一次确认往返一致。实测三步均通过的三份 policy 文件见本文资源目录。

Q11:256MB 堆够解析多大策略文档? A:本实验全部示例 -Xms256m -Xmx256m 运行(jps -v 可验证),KB 级策略文档内存占用可忽略。嵌套深度与 optional 断言数量是指数展开的根源(2^n 条 alternative),超大规模策略(n>15)需评估展开后的规模。

七、参考链接

  1. WS-Policy 1.5(W3C Recommendation,框架与算子语义):https://www.w3.org/TR/ws-policy/
  2. WS-PolicyAttachment(Optional/Ignorable/附着定义):https://www.w3.org/Submission/WS-PolicyAttachment/
  3. Apache Neethi 官方文档:https://neethi.apache.org/
  4. Neethi 3.2.2 源码(normalize/merge 实现):https://github.com/apache/neethi
  5. WS-SecurityPolicy 1.2(嵌套策略标准示例来源):http://docs.oasis-open.org/ws-sx/ws-securitypolicy/v1.2/ws-securitypolicy.html
  6. Apache CXF WS-Policy 框架(Neethi 生产级用法):https://cxf.apache.org/docs/ws-policy.html
  7. Java StAX(XMLStreamWriter)API:https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/javax/xml/stream/XMLStreamWriter.html