Spring Boot配置SOAP请求安全Header遇Must Understand错误求助
Spring Boot调用SOAP服务时添加WS-Security Header的问题
我对SOAP服务不太熟悉,正在尝试在Spring Boot应用中调用某SOAP服务。程序能运行,但因为无法按如下目标SOAP请求设置安全Header而抛出异常:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webservices.service.company.com/"> <soapenv:Header xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:Security> <wsse:UsernameToken> <wsse:Username>username</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">Pa$$wrd</wsse:Password> </wsse:UsernameToken> </wsse:Security> </soapenv:Header> <soapenv:Body> ... </soapenv:Body> </soapenv:Envelope>
我的配置类如下:
@Configuration public class SoapClientConfig { @Bean public Jaxb2Marshaller marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath("com.company.ws.wsdl"); return marshaller; } @Bean public SoapClient soapClient(Jaxb2Marshaller marshaller) { SoapClient client = new SoapClient(); client.setDefaultUri("localhost:8080/service/myservice"); client.setMarshaller(marshaller); client.setUnmarshaller(marshaller); return client; } }
SoapClient类:
@Component public class SoapClient extends WebServiceGatewaySupport { public CustSearchResponse getCustomer(CustomerSearchRequest custRequest) { ObjectFactory objectFactory = new ObjectFactory(); JAXBElement<CustomerSearchRequest> request = objectFactory.createCustomerSearchRequest(custRequest); JAXBElement response = (JAXBElement)getWebServiceTemplate().marshalSendAndReceive( request); return (CustSearchResponse) response.getValue(); } }
我查了一些示例,但觉得太复杂,不知道怎么适配我的SOAP Header,求建议。
更新:添加安全拦截器后的错误
尝试添加如下Wss4jSecurityInterceptor:
@Bean public Wss4jSecurityInterceptor securityInterceptor() { Wss4jSecurityInterceptor security = new Wss4jSecurityInterceptor(); security.setSecurementActions(WSHandlerConstants.TIMESTAMP + " " + WSHandlerConstants.USERNAME_TOKEN); security.setSecurementPasswordType(WSConstants.PW_TEXT); security.setSecurementUsername("username"); security.setSecurementPassword("password"); return security; }
并更新SoapClient Bean:
@Bean public SoapClient soapClient(Jaxb2Marshaller marshaller) { SoapClient client = new SoapClient(); client.setDefaultUri("localhost:8080/service/myservice"); client.setMarshaller(marshaller); client.setUnmarshaller(marshaller); client.setInterceptors(new ClientInterceptor[]{ securityInterceptor() }); return client; }
现在出现如下错误:
org.springframework.ws.soap.client.SoapFaultClientException: 对SOAP-ENV头{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}Security的Must Understand检查失败 at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:38) at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:795) at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:605) at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:542) at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:394) at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:388) at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:378)
解决办法
问题根因
这个错误是因为SOAP服务端要求Security头必须标记为"必须理解"(MustUnderstand=true),但当前拦截器生成的头没有正确设置该属性;另外你添加的TIMESTAMP动作可能是服务端不需要的,也会引发处理异常。
具体修复步骤
修改安全拦截器配置
移除不需要的TIMESTAMP动作(如果服务端没要求),同时显式设置Security头的MustUnderstand属性:@Bean public Wss4jSecurityInterceptor securityInterceptor() { Wss4jSecurityInterceptor security = new Wss4jSecurityInterceptor(); // 仅保留USERNAME_TOKEN,去掉服务端不需要的TIMESTAMP security.setSecurementActions(WSHandlerConstants.USERNAME_TOKEN); security.setSecurementPasswordType(WSConstants.PW_TEXT); security.setSecurementUsername("username"); security.setSecurementPassword("password"); // 设置Security头的MustUnderstand属性为true Map<String, Object> securementProps = new HashMap<>(); securementProps.put(WSHandlerConstants.MUST_UNDERSTAND, "true"); security.setSecurementProperties(securementProps); return security; }修正SoapClient的Bean配置
确保拦截器被正确注入,同时补全请求地址的协议前缀(比如http://):@Bean public SoapClient soapClient(Jaxb2Marshaller marshaller, Wss4jSecurityInterceptor securityInterceptor) { SoapClient client = new SoapClient(); client.setDefaultUri("http://localhost:8080/service/myservice"); client.setMarshaller(marshaller); client.setUnmarshaller(marshaller); client.setInterceptors(new ClientInterceptor[]{securityInterceptor}); return client; }核对服务端要求
- 确认服务端是否需要Timestamp,不需要就坚决移除,避免多余头导致服务端无法处理。
- 确认密码的Type属性和服务端要求一致(你当前用的
PasswordText和目标请求一致,这部分没问题)。
备选方案:手动构建SOAP头
如果拦截器方式仍有问题,可以直接手动构建Security头:在SoapClient的
getCustomer方法中添加头构建逻辑:public CustSearchResponse getCustomer(CustomerSearchRequest custRequest) { ObjectFactory objectFactory = new ObjectFactory(); JAXBElement<CustomerSearchRequest> request = objectFactory.createCustomerSearchRequest(custRequest); // 手动生成WS-Security头 SoapHeader soapHeader = getWebServiceTemplate().getMessageFactory().createWebServiceMessage().getSoapHeader(); String securityHeaderXml = "<wsse:Security xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" soapenv:mustUnderstand=\"1\" xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<wsse:UsernameToken>" + "<wsse:Username>username</wsse:Username>" + "<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">Pa$$wrd</wsse:Password>" + "</wsse:UsernameToken>" + "</wsse:Security>"; try { Source source = new StringSource(securityHeaderXml); soapHeader.getResult().setSystemId(""); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(source, soapHeader.getResult()); } catch (Exception e) { throw new RuntimeException("生成SOAP安全头失败", e); } JAXBElement response = (JAXBElement)getWebServiceTemplate().marshalSendAndReceive(request); return (CustSearchResponse) response.getValue(); }
内容的提问来源于stack exchange,提问作者Thomson Mathew
相关产品推荐
相关产品推荐

