使用Camel CXF消费带HTTPS与安全头的SOAP API遇时间戳验证错误
Camel-CXF消费SOAP服务时PolicyException(Timestamp/IncludeTimestamp)问题解决
问题重现
使用Camel 2.20.3 + CXF 3.2.6消费带安全策略的SOAP服务时,抛出以下异常:
org.apache.cxf.ws.policy.PolicyException: These policy alternatives can not be satisfied: {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}TransportBinding: Received Timestamp does not match the requirements {http://schemas.xmlsoap.org/ws/2005/07/securitypolicy}IncludeTimestamp
SOAP响应返回a:InvalidSecurity错误,而SoapUI可正常调用该服务。WSDL安全策略要求:
- HTTPS传输(
HttpsToken) - 请求必须包含Timestamp(
IncludeTimestamp) - 带签名的UsernameToken认证(
SignedSupportingTokens+WssUsernameToken10)
核心原因
- DataFormat配置冲突:使用
MESSAGE格式时,Camel直接传递原始SOAP消息,CXF无法自动触发WSDL安全策略对应的拦截器,导致未生成Timestamp和签名的UsernameToken。 - 安全参数配置不完整:仅设置用户名密码未满足
IncludeTimestamp和SignedSupportingTokens的策略要求,缺少Timestamp生成、签名相关的WSS4J配置。 - 版本兼容性:Camel 2.20.3默认依赖CXF 3.1.x,手动引入CXF 3.2.6可能存在版本冲突。
解决方案
方案1:调整CXF端点配置,启用自动策略处理
1. 修改Camel路由
将dataFormat从MESSAGE改为POJO,让CXF自动处理消息转换和安全策略执行,同时移除路由中手动设置的username/password参数:
from("direct:userdata.soap.requests") .to("cxf://{{SOAP_URL}}" + "?serviceClass=org.tempuri.IUserManagementService" + "&serviceName={http://tempuri.org/}UserManagementService" + "&endpointName={http://tempuri.org/}BasicHttpBinding_IUserManagementService" + "&wsdlURL={{WSDL_URL}}" + "&dataFormat=POJO" + "&allowStreaming=false");
2. 完善请求上下文的WSS4J配置
在发送请求时,添加Timestamp生成、UsernameToken签名的相关参数:
private Object sendRequest(Object request, String operationName) throws Exception{ // 改用InOut模式,匹配请求-响应的SOAP交互 Exchange inExchange = endpoint.createExchange(ExchangePattern.InOut); inExchange.getIn().setHeader(CxfConstants.OPERATION_NAME, operationName); inExchange.getIn().setHeader(CxfConstants.OPERATION_NAMESPACE, "http://tempuri.org/"); inExchange.getIn().setBody(request); Map<String, Object> context = new HashMap<>(); // 配置WSS4J动作:生成Timestamp + 签名UsernameToken context.put("ws-security.action", "UsernameToken Timestamp"); context.put("ws-security.username", soapUsername); context.put("ws-security.password", soapPassword); // 对应WSDL的WssUsernameToken10,使用明文密码(若服务端要求Digest则改为PasswordDigest) context.put("ws-security.passwordType", "PasswordText"); // 签名回调处理器,用于获取签名密码 context.put("ws-security.callback-handler", new PasswordCallback(soapPassword)); // 签名密钥配置文件(需放在classpath下) context.put("ws-security.signature.properties", "client.properties"); inExchange.getIn().setHeader(Client.REQUEST_CONTEXT, context); Exchange outExchange = channel.send(inExchange); Object result = outExchange.getIn().getBody(Object.class); if(result instanceof FaultException){ throw (FaultException) result; } return result; }
3. 实现密码回调处理器
public class PasswordCallback implements CallbackHandler { private final String password; public PasswordCallback(String password) { this.password = password; } @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { WSPasswordCallback pc = (WSPasswordCallback) callbacks[0]; pc.setPassword(password); } }
4. 添加签名密钥配置文件client.properties
在classpath下创建该文件,使用自签名证书即可(服务端若仅验证签名存在性,无需信任证书):
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin org.apache.ws.security.crypto.merlin.keystore.type=JKS org.apache.ws.security.crypto.merlin.keystore.password=your-keystore-pass org.apache.ws.security.crypto.merlin.keystore.file=client.jks
方案2:手动配置WSS4J拦截器
若自动策略处理失效,可手动添加WSS4J输出拦截器强制生成安全头:
1. 修改Camel路由,引用自定义拦截器
from("direct:userdata.soap.requests") .to("cxf://{{SOAP_URL}}" + "?serviceClass=org.tempuri.IUserManagementService" + "&serviceName={http://tempuri.org/}UserManagementService" + "&endpointName={http://tempuri.org/}BasicHttpBinding_IUserManagementService" + "&wsdlURL={{WSDL_URL}}" + "&dataFormat=POJO" + "&allowStreaming=false" + "&outInterceptors=#wss4jOutInterceptor");
2. 在Blueprint/Spring中定义拦截器
<bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor"> <constructor-arg> <map> <entry key="action" value="UsernameToken Timestamp"/> <entry key="username" value="{{SOAP_USERNAME}}"/> <entry key="password" value="{{SOAP_PASSWORD}}"/> <entry key="passwordType" value="PasswordText"/> <entry key="signaturePropFile" value="client.properties"/> <entry key="callbackHandler" ref="passwordCallback"/> </map> </constructor-arg> </bean> <bean id="passwordCallback" class="com.your.package.PasswordCallback"> <constructor-arg value="{{SOAP_PASSWORD}}"/> </bean>
关键注意事项
- 版本统一:将CXF依赖版本调整为Camel 2.20.3默认的3.1.12,避免版本冲突。
- 日志验证:添加CXF日志拦截器,查看发送的SOAP消息是否包含
<wsu:Timestamp>和带签名的<wsse:UsernameToken>元素,对比SoapUI的请求消息排查差异。
内容的提问来源于stack exchange,提问作者Raphi1
相关产品推荐
相关产品推荐

