Apache WSS4J 3.0.6 快速入门与集成示例
用 10 分钟在 Maven 工程中引入 Apache WSS4J 3.0.6 并跑通第一个 WS-Security 程序,覆盖依赖引入与 Init.init() 关键调用、签名验证最小闭环、漏初始化算法映射表等实测高频坑,并给出与 2.4.4/4.0.2 的版本兼容建议,全部已在 Ubuntu 22.04 实测。
Apache WSS4J 3.0.6 快速入门与集成示例
目标:10 分钟内在 Maven 工程中引入 Apache WSS4J 3.0.6,跑通第一个「构造报文 → 签名 → 验证」的最小闭环;掌握依赖引入与初始化的高频坑;确认 JDK 兼容基线;最后给出与 2.4.4/4.0.2 的版本选型建议。以下全部命令与回显均已在 Ubuntu 22.04(x86_64)、OpenJDK 17.0.20.1 实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- JDK:OpenJDK 17.0.20.1(
java -version实测) - 构建工具:Apache Maven 3.6.3
- 目标库:Apache WSS4J 3.0.6(
org.apache.wss4j:wss4j-ws-security-dom:3.0.6,3.x 主线最新稳定版) - 传递依赖:实测 36 个 jar(核心:wss4j-ws-security-common 3.0.6、xmlsec 3.0.6、opensaml-saml-impl 4.3.2、bcprov-jdk18on 1.82、guava 33.7.1-jre、slf4j-api 2.0.19)
- Maven 示例工程目录:
/root/wss4j-demo
确认 JDK 与 Maven 版本基线:
java -version 2>&1 | head -1
mvn -v | head -3
输出:
openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
💡 说明:WSS4J 3.0.6 官方基线为 JDK 11(父 POM
javaVersion=11),JDK 17 完全兼容(本文即按 17 实测);JDK 8 环境请改用 2.4.4。没有 Maven 的离线环境改用 jar 直用方式,见第 1 篇《Ubuntu 22.04 安装 Apache WSS4J 指南》第五章。
二、10 分钟最小可用
本节从空目录到跑通「签名 + 验证」闭环共 4 步,全程约 10 分钟:建工程(1 分钟)→ 写 pom(2 分钟)→ 生成密钥库与程序(4 分钟)→ 编译运行验证(3 分钟,首次下载 36 个依赖 jar 视网速)。
2.1 创建工程并引入依赖
mkdir -p /root/wss4j-demo/src/main/java/demo
/root/wss4j-demo/pom.xml 全文(最小可用配置,直接照抄):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>wss4j-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.wss4j</groupId>
<artifactId>wss4j-ws-security-dom</artifactId>
<version>3.0.6</version>
</dependency>
<!-- 仅用于让 slf4j 输出可读日志,不影响功能 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
</plugins>
</build>
</project>
两个必做项,缺一会在 2.3 步踩坑:
- 显式固定 maven-compiler-plugin 为 3.11.0:Maven 3.6.3 默认绑定的 compiler 插件是 3.1,不认识
maven.compiler.release属性,会按 source/target=1.5 编译并被 JDK 17 拒绝(报Source option 5 is no longer supported)。 - 声明
project.build.sourceEncoding=UTF-8:源码含中文注释/字符串时防止按 POSIX 默认编码编译报unmappable character。
依赖坐标说明:groupId 固定 org.apache.wss4j、artifactId wss4j-ws-security-dom、版本 3.0.6。声明这一项即引入全部能力,BouncyCastle 经 cryptacular 传递带入,无需单独声明。
2.2 生成密钥库与编写第一个程序
签名验证需要一对自签密钥(口令统一 q1w2e3r4):
mkdir -p /root/keystores && cd /root/keystores
keytool -genkeypair -alias client -keyalg RSA -keysize 2048 -sigalg SHA256withRSA \
-validity 3650 -dname "CN=client.demows.example,O=Demo,L=Beijing,C=CN" \
-keystore client.jks -storetype JKS -storepass q1w2e3r4 -keypass q1w2e3r4
keytool -exportcert -alias client -keystore client.jks -storepass q1w2e3r4 -rfc -file client.cer
keytool -importcert -alias client -file client.cer -keystore trust.jks -storetype JKS -storepass q1w2e3r4 -noprompt
Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 3650
for: CN=client.demows.example, O=Demo, L=Beijing, C=CN
Certificate stored in file <client.cer>
Certificate was added to keystore
两个密钥库的分工:client.jks 存私钥(签名方加载),trust.jks 只存证书(验证方加载,决定「信谁的签名」)。
/root/wss4j-demo/src/main/java/demo/QuickStart.java 全文(默认程序入口,直接复制):
package demo;
import org.apache.wss4j.common.WSEncryptionPart;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.CryptoFactory;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.engine.WSSecurityEngine;
import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
import org.apache.wss4j.dom.handler.RequestData;
import org.apache.wss4j.dom.handler.WSHandlerResult;
import org.apache.wss4j.dom.message.WSSecHeader;
import org.apache.wss4j.dom.message.WSSecSignature;
import org.apache.xml.security.Init;
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.w3c.dom.Document;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class QuickStart {
static final String SOAP_NS = "http://schemas.xmlsoap.org/soap/envelope/";
static final String PASSWORD = "q1w2e3r4";
public static void main(String[] args) throws Exception {
Init.init(); // 关键第一步:初始化算法映射表,漏掉会在加密等路径报错
// 1) 构造 SOAP 报文(纯 DOM,不依赖任何应用服务器)
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
org.w3c.dom.Element envelope = doc.createElementNS(SOAP_NS, "soap:Envelope");
envelope.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:soap", SOAP_NS);
doc.appendChild(envelope);
org.w3c.dom.Element body = doc.createElementNS(SOAP_NS, "soap:Body");
envelope.appendChild(body);
org.w3c.dom.Element payload = doc.createElementNS("http://demo.example/order", "ord:submitOrder");
payload.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ord", "http://demo.example/order");
payload.setTextContent("order-1001|item=SSD-2T|qty=3|amount=1299.00");
body.appendChild(payload);
// 2) 插入 wsse:Security 头并签名 Body
WSSecHeader secHeader = new WSSecHeader(doc);
secHeader.insertSecurityHeader();
WSSecSignature sign = new WSSecSignature(secHeader);
sign.setUserInfo("client", PASSWORD);
sign.setKeyIdentifierType(WSConstants.BST_DIRECT_REFERENCE);
sign.setSignatureAlgorithm(WSConstants.RSA_SHA256);
sign.setDigestAlgo(MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256);
sign.setSigCanonicalization(WSConstants.C14N_EXCL_OMIT_COMMENTS);
sign.prepare(crypto("/root/keystores/client.jks", "JKS", null));
List<WSEncryptionPart> parts = new ArrayList<>();
parts.add(new WSEncryptionPart("Body", SOAP_NS, "Content"));
sign.computeSignature(sign.addReferencesToSign(parts));
sign.prependBSTElementToHeader();
System.out.println("签名值字节数 = " + sign.getSignatureValue().length);
// 3) 验证侧:WSSecurityEngine 处理签名
WSSecurityEngine engine = new WSSecurityEngine();
RequestData reqData = new RequestData();
reqData.setSigVerCrypto(crypto("/root/keystores/trust.jks", "JKS", null));
reqData.setCallbackHandler(callbacks -> {
for (Callback cb : callbacks) {
if (cb instanceof WSPasswordCallback) {
((WSPasswordCallback) cb).setPassword(PASSWORD);
} else {
throw new UnsupportedCallbackException(cb);
}
}
});
WSHandlerResult result = engine.processSecurityHeader(doc, reqData);
List<String> actions = new ArrayList<>();
for (WSSecurityEngineResult r : result.getResults()) {
int a = (Integer) r.get(WSSecurityEngineResult.TAG_ACTION);
actions.add(a == WSConstants.SIGN ? "SIGN" : a == WSConstants.BST ? "BST" : String.valueOf(a));
}
System.out.println("验证通过,处理到的安全动作 = " + actions);
}
static Crypto crypto(String file, String type, String provider) throws WSSecurityException {
Properties p = new Properties();
p.setProperty("org.apache.wss4j.crypto.provider", "org.apache.wss4j.common.crypto.Merlin");
p.setProperty("org.apache.wss4j.crypto.merlin.keystore.type", type);
p.setProperty("org.apache.wss4j.crypto.merlin.keystore.file", file);
p.setProperty("org.apache.wss4j.crypto.merlin.keystore.password", PASSWORD);
if (provider != null) {
p.setProperty("org.apache.wss4j.crypto.merlin.keystore.provider", provider);
}
return CryptoFactory.getInstance(p);
}
}
程序骨架解读:WSS4J 3.0.6 的 DOM 层直接吃 org.w3c.dom.Document——WSSecHeader 负责插入 wsse:Security 头,WSSecSignature 负责签名(prepare → 选引用 → 计算 → 放入证书),验证侧 WSSecurityEngine.processSecurityHeader(Document, RequestData) 一次处理报文头里全部安全元素。Merlin 是 WSS4J 内置的 Crypto 实现,通过属性指向密钥库。
2.3 编译与运行
cd /root/wss4j-demo && mvn -q -B compile
echo "EXIT=$?"
输出:
EXIT=0
运行:
mvn -q -B exec:java -Dexec.mainClass=demo.QuickStart
输出:
签名值字节数 = 256
验证通过,处理到的安全动作 = [SIGN, BST]
2.4 验证点与预期结果
拿到以下结果即集成成功,任一不符按第三章排查:
| 检查项 | 预期(实测值) | 不符时排查 |
|---|---|---|
mvn -q -B compile 退出码 | EXIT=0 | 报 Source option 5 → 3.2 节 |
| 签名值字节数 | 256(RSA 2048 位) | NoSuchAlgorithmException 等 → 3.1 节 |
| 处理到的安全动作 | [SIGN, BST] | WSSecurityException → 3.1 节;中文乱码 → 3.3 节 |
| 依赖树首层 | wss4j-ws-security-dom:jar:3.0.6:compile | 检查 pom 坐标拼写 |
手算核对:RSA 2048 位签名值恒为 256 字节;SIGN 表示签名验证通过,BST(BinarySecurityToken)表示验证侧接收并处理了报文内直附的客户端证书。
三、Maven 工程常见坑
以下三个坑在 WSS4J 3.0.6 集成中出现频率最高,均为实测复现,给出定位与修复步骤。
3.1 坑一:漏调 Init.init(),加密路径报「算法不支持」
现象:签名相关操作正常,一调用加密(如 WSSecEncrypt)就抛:
org.apache.wss4j.common.ext.WSSecurityException: unsupported key transport encryption algorithm: No such algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
定位:org.apache.xml.security.Init.init() 没有被调用,Santuario 的 JCEMapper 算法映射表是空的。用两行探针实测对比(同一 JVM 内):
System.out.println(org.apache.xml.security.algorithms.JCEMapper.translateURItoJCEID("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"));
org.apache.xml.security.Init.init();
System.out.println(org.apache.xml.security.algorithms.JCEMapper.translateURItoJCEID("http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"));
beforeInit oaep map = null
afterInit oaep map = RSA/ECB/OAEPPadding
初始化前映射为 null,密钥传输直接失败;初始化后 rsa-oaep-mgf1p 映射到 RSA/ECB/OAEPPadding,加密正常。修复:main 方法第一行加 Init.init();(该方法幂等,重复调用无害)。签名路径之所以「看起来没事」,是因为签名算法解析不走这条密钥传输映射,坑只在加密/密钥协商时爆发。
3.2 坑二:老 compiler 插件不认识 release 参数
现象:mvn compile 报错(同时含两行):
[ERROR] Source option 5 is no longer supported. Use 7 or later.
[ERROR] Target option 5 is no longer supported. Use 7 or later.
定位:Maven 3.6.3 默认绑定的 maven-compiler-plugin 版本是 3.1,它不认识 pom 里的 maven.compiler.release 属性,退回按 source/target=1.5 处理,被 JDK 17 的 javac 拒绝。修复:在 pom 的 <build><plugins> 中显式固定 3.11.0(见 2.1 节 pom 全文)。该坑在老版本 Maven(如 CentOS 8 的 3.5.4)上同样存在,修复方式相同。
3.3 坑三:编码与 locale
两个环节都可能踩:
- 编译期:源码含中文而未声明编码时,javac 按平台默认编码(POSIX/C locale 下为 ANSI_X3.4-1968)解码,报
unmappable character for encoding ASCII。修复:pom 设project.build.sourceEncoding=UTF-8(Maven 工程),或 javac 加-encoding UTF-8(命令行编译)。 - 运行期:POSIX/C locale 下
System.out默认输出编码为 ASCII,中文变问号(实测在 CentOS 8 与默认 locale 的会话中均复现)。修复:运行前export LANG=C.UTF-8,或运行时加 JVM 系统属性-Dfile.encoding=UTF-8。编码问题只影响显示,不影响签名/加密正确性。
四、JDK 兼容与版本选型
三线版本与 JDK 基线全部来自 Maven Central 父 POM 实测:
| 版本线 | 最新版 | JDK 基线 | 本文实测结论 |
|---|---|---|---|
| 2.4.x | 2.4.4 | JDK 8(maven.compiler.target=1.8) | 服务器只有 JDK 8 时的唯一选择,API 与 3.x 同名同用法 |
| 3.0.x | 3.0.6 | JDK 11(javaVersion=11) | 本文实测通过(JDK 17.0.20.1,签名/加密/时间戳/UT 全链路) |
| 4.0.x | 4.0.2 | JDK 17(javaVersion=17) | 全量最新;API 与 3.0.x 兼容度高,迁移成本低 |
按场景落到具体建议:
- 新项目、需要长期维护 → 3.0.6(本文方式集成,10 分钟可用;BouncyCastle 已随传递依赖就位)。
- 跟随 CXF 最新大版本、全员 JDK 17 → 评估 4.0.2,坐标只改版本号先编译验证。
- 遗留 JDK 8 服务器 → 2.4.4;注意 2.4.x 与 3.x 的传递依赖不同(3.x 内置 BouncyCastle 1.82 与 jakarta 命名空间组件),不要混排。
- 只需要加密报文互操作、两端 JDK 版本不同 → 无影响:WS-Security 报文层面互认,双方算法支持一致即可(RSA-SHA256、AES-128-GCM 等在三个版本线均有)。
五、参考链接
- Apache WSS4J 官方主页:https://ws.apache.org/wss4j/
- WSS4J 3.0.6 Maven 中央仓库坐标:https://repo1.maven.org/maven2/org/apache/wss4j/wss4j-ws-security-dom/
- WSS4J 源码仓库(API 问答时对源码最有效):https://github.com/apache/wss4j
- Apache Santuario JCEMapper 算法映射说明:https://santuario.apache.org/javadocs/3.0/org/apache/xml/security/algorithms/JCEMapper.html
- 本专栏前置篇:《Ubuntu 22.04 安装 Apache WSS4J 指南》(环境与密钥库基础)、《CentOS 8 安装 Apache WSS4J 指南》(el8 换源与 JAVA_HOME)