Apache Neethi 3.2.2 WS-Policy 配置详解
详解 Apache Neethi 3.2.2 下 WS-Policy 文档的元素语法、Optional/Ignorable 语义、合并与规范化算法及生产策略模板,全部示例实测验证。
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) |
| JDK | OpenJDK 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="true | false" | 断言元素 | 断言可选(可要可不要) |
| wsp:Ignorable="true | false" | 断言元素 | 不理解时可忽略 |
| wsu:Id | 根元素 | 策略 ID | Policy.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)需评估展开后的规模。
七、参考链接
- WS-Policy 1.5(W3C Recommendation,框架与算子语义):https://www.w3.org/TR/ws-policy/
- WS-PolicyAttachment(Optional/Ignorable/附着定义):https://www.w3.org/Submission/WS-PolicyAttachment/
- Apache Neethi 官方文档:https://neethi.apache.org/
- Neethi 3.2.2 源码(normalize/merge 实现):https://github.com/apache/neethi
- WS-SecurityPolicy 1.2(嵌套策略标准示例来源):http://docs.oasis-open.org/ws-sx/ws-securitypolicy/v1.2/ws-securitypolicy.html
- Apache CXF WS-Policy 框架(Neethi 生产级用法):https://cxf.apache.org/docs/ws-policy.html
- Java StAX(XMLStreamWriter)API:https://docs.oracle.com/en/java/javase/17/docs/api/java.xml/javax/xml/stream/XMLStreamWriter.html