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 WSS4JApache WSS4J2026/10/15

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.x2.4.4JDK 8(maven.compiler.target=1.8)服务器只有 JDK 8 时的唯一选择,API 与 3.x 同名同用法
3.0.x3.0.6JDK 11(javaVersion=11)本文实测通过(JDK 17.0.20.1,签名/加密/时间戳/UT 全链路)
4.0.x4.0.2JDK 17(javaVersion=17)全量最新;API 与 3.0.x 兼容度高,迁移成本低

按场景落到具体建议:

  1. 新项目、需要长期维护 → 3.0.6(本文方式集成,10 分钟可用;BouncyCastle 已随传递依赖就位)。
  2. 跟随 CXF 最新大版本、全员 JDK 17 → 评估 4.0.2,坐标只改版本号先编译验证。
  3. 遗留 JDK 8 服务器 → 2.4.4;注意 2.4.x 与 3.x 的传递依赖不同(3.x 内置 BouncyCastle 1.82 与 jakarta 命名空间组件),不要混排。
  4. 只需要加密报文互操作、两端 JDK 版本不同 → 无影响:WS-Security 报文层面互认,双方算法支持一致即可(RSA-SHA256、AES-128-GCM 等在三个版本线均有)。

五、参考链接

  1. Apache WSS4J 官方主页:https://ws.apache.org/wss4j/
  2. WSS4J 3.0.6 Maven 中央仓库坐标:https://repo1.maven.org/maven2/org/apache/wss4j/wss4j-ws-security-dom/
  3. WSS4J 源码仓库(API 问答时对源码最有效):https://github.com/apache/wss4j
  4. Apache Santuario JCEMapper 算法映射说明:https://santuario.apache.org/javadocs/3.0/org/apache/xml/security/algorithms/JCEMapper.html
  5. 本专栏前置篇:《Ubuntu 22.04 安装 Apache WSS4J 指南》(环境与密钥库基础)、《CentOS 8 安装 Apache WSS4J 指南》(el8 换源与 JAVA_HOME)