You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于wss4j 1.6.5版本为SOAP请求添加BinarySecurityToken的技术实现问询

我明白你现在卡在这个老旧SOAP客户端的适配问题上了——wss4j 1.6.5的资料确实稀缺,再加上原开发团队不在,确实头疼。结合你提供的代码和需求,我来给你一步步拆解解决方案:

核心思路

因为wss4j 1.6.5对自定义BinarySecurityToken的支持不够灵活,且资料有限,我们采用手动修改SOAP DOM文档的方式来实现需求,这种方式更直观可控,也避开了深入wss4j内部复杂逻辑的坑。


1. 给Security节点添加BinarySecurityToken

我们创建一个自定义的OutHandler,在WSS4J生成UsernameToken之后,手动插入BinarySecurityToken元素:

import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.Handler;
import org.codehaus.xfire.handler.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.namespace.QName;
import java.util.UUID;

public class AddBinarySecurityTokenHandler implements Handler {
    private String authToken;
    // 可选:如果需要Base64编码,这里可以提前处理
    private boolean needBase64Encode;

    public AddBinarySecurityTokenHandler(String authToken, boolean needBase64Encode) {
        this.authToken = authToken;
        this.needBase64Encode = needBase64Encode;
        setPhase(Phase.WRITE);
        // 确保在WSS4JOutHandler之后执行,此时Security节点已生成
        after(org.apache.ws.security.handler.WSS4JOutHandler.class.getName());
    }

    @Override
    public void invoke(MessageContext context) throws Exception {
        // 获取DOM文档(依赖你已配置的DOMOutHandler)
        Document doc = (Document) context.getOutMessage().getProperty("dom.message");
        if (doc == null) return;

        // 定位wsse:Security节点
        QName securityQName = new QName(
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
            "Security",
            "wsse"
        );
        NodeList securityNodes = doc.getElementsByTagNameNS(securityQName.getNamespaceURI(), securityQName.getLocalPart());
        if (securityNodes.getLength() == 0) return;
        Element securityElement = (Element) securityNodes.item(0);

        // 构造BinarySecurityToken元素
        Element bstElement = doc.createElementNS(securityQName.getNamespaceURI(), "wsse:BinarySecurityToken");
        // 设置属性
        bstElement.setAttribute("ValueType", "authToken");
        bstElement.setAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        // 生成随机wsu:Id
        String bstId = "SecurityToken-" + UUID.randomUUID().toString();
        bstElement.setAttributeNS(
            "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
            "wsu:Id",
            bstId
        );
        // 设置内容(如果需要Base64编码则处理)
        String tokenContent = needBase64Encode 
            ? javax.xml.bind.DatatypeConverter.printBase64Binary(authToken.getBytes("UTF-8")) 
            : authToken;
        bstElement.setTextContent(tokenContent);

        // 将元素添加到Security节点末尾
        securityElement.appendChild(bstElement);
    }

    // 实现Handler接口的其他方法
    @Override public void setPhase(String phase) {}
    @Override public String getPhase() { return Phase.WRITE; }
    @Override public void before(String handlerName) {}
    @Override public void after(String handlerName) {}
    @Override public boolean isFaultHandler() { return false; }
}

然后在你的enableUsernameTokenEClaims方法中添加这个Handler:

private void enableUsernameTokenEClaims( Client client, String username, String authToken ) {
    final java.util.Map<String, Object> usernameTokenConfig = new HashMap<String, Object>();
    usernameTokenConfig.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
    usernameTokenConfig.put(WSHandlerConstants.USER, username);
    usernameTokenConfig.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST);

    final RxPasswordHandler pwdHandler = new RxPasswordHandler();
    pwdHandler.setPassword(authToken, true);
    usernameTokenConfig.put(WSHandlerConstants.PW_CALLBACK_REF, pwdHandler);

    client.addOutHandler(new WSS4JOutHandler(usernameTokenConfig));
    // 添加自定义Handler,第二个参数根据需求决定是否需要Base64编码
    client.addOutHandler(new AddBinarySecurityTokenHandler(authToken, true));
}

2. 添加自定义authMethod元素到SOAP Header

同样创建一个自定义Handler,直接在SOAP Header末尾插入ehr:authMethod元素:

import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.Handler;
import org.codehaus.xfire.handler.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.namespace.QName;

public class AddAuthMethodHandler implements Handler {
    private String ehrNamespace;
    private String authMethodValue;

    public AddAuthMethodHandler(String ehrNamespace, String authMethodValue) {
        this.ehrNamespace = ehrNamespace;
        this.authMethodValue = authMethodValue;
        setPhase(Phase.WRITE);
        // 确保在地址头处理完成后执行
        after(RxAddressingOutHandler.class.getName());
    }

    @Override
    public void invoke(MessageContext context) throws Exception {
        Document doc = (Document) context.getOutMessage().getProperty("dom.message");
        if (doc == null) return;

        // 定位soap:Header节点
        QName headerQName = new QName(
            "http://schemas.xmlsoap.org/soap/envelope/",
            "Header",
            "soap"
        );
        NodeList headerNodes = doc.getElementsByTagNameNS(headerQName.getNamespaceURI(), headerQName.getLocalPart());
        if (headerNodes.getLength() == 0) return;
        Element headerElement = (Element) headerNodes.item(0);

        // 构造authMethod元素
        Element authMethodElement = doc.createElementNS(ehrNamespace, "ehr:authMethod");
        authMethodElement.setTextContent(authMethodValue);

        // 添加到Header末尾
        headerElement.appendChild(authMethodElement);
    }

    // 实现Handler接口的其他方法
    @Override public void setPhase(String phase) {}
    @Override public String getPhase() { return Phase.WRITE; }
    @Override public void before(String handlerName) {}
    @Override public void after(String handlerName) {}
    @Override public boolean isFaultHandler() { return false; }
}

在sendRequest方法中添加这个Handler(放在enableWSAddressing之后):

this.enableWSAddressing( client, action, msgId, receiverNetAddr, senderNetAddr, toEndpointID, fromEndpointID );
// 替换第一个参数为服务端要求的ehr命名空间,第二个参数固定为"authToken"
client.addOutHandler(new AddAuthMethodHandler("some_text_value", "authToken"));

关键注意事项

  1. 命名空间准确性:确保所有元素的命名空间(wsse、wsu、ehr)和服务端要求完全一致,这是SOAP请求成功的关键。
  2. Handler执行顺序:通过after()方法确保自定义Handler在对应的基础Handler之后执行,避免节点还未生成就去修改。
  3. Base64编码:根据服务端要求决定是否对authToken进行Base64编码,代码中已预留开关。
  4. 测试验证:修改后一定要通过抓包或输出XML的方式,检查生成的SOAP Header是否和期望示例完全一致,包括元素顺序、属性值等。

内容的提问来源于stack exchange,提问作者MattWeiler

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 15:08:15