基于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"));
关键注意事项
- 命名空间准确性:确保所有元素的命名空间(wsse、wsu、ehr)和服务端要求完全一致,这是SOAP请求成功的关键。
- Handler执行顺序:通过
after()方法确保自定义Handler在对应的基础Handler之后执行,避免节点还未生成就去修改。 - Base64编码:根据服务端要求决定是否对authToken进行Base64编码,代码中已预留开关。
- 测试验证:修改后一定要通过抓包或输出XML的方式,检查生成的SOAP Header是否和期望示例完全一致,包括元素顺序、属性值等。
内容的提问来源于stack exchange,提问作者MattWeiler
相关产品推荐
相关产品推荐

