Spring WS调用无证书SOAP服务遇401未授权问题求助
嘿,我之前也踩过这个坑!既然SoapUI能正常发起请求,说明服务端的认证逻辑没问题,问题肯定出在Spring WS客户端传递认证信息的方式和SoapUI不一致。咱们一步步来搞定它:
首先,你得先确认SoapUI里的“请求属性”到底是什么——打开SoapUI的请求,切换到「Raw」标签,看看用户名和密码是放在HTTP请求头里,还是嵌入到SOAP Header里了?这两种情况的解决方式不一样。
情况1:认证信息放在HTTP请求头里(SoapUI里的请求属性是自定义HTTP头)
如果Raw视图里能看到类似X-Username: your-user、X-Password: your-pass的请求头,那咱们需要给Spring WS的客户端加一个拦截器,在请求发送前把这些头加上:
@Bean public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) { WebServiceTemplate template = new WebServiceTemplate(marshaller); template.setInterceptors(new ClientInterceptor[]{ new ClientInterceptor() { @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { // 获取当前的HTTP连接 TransportContext context = TransportContextHolder.getTransportContext(); HttpUrlConnection connection = (HttpUrlConnection) context.getConnection(); try { // 替换成你在SoapUI里设置的请求属性名和值 connection.addRequestProperty("X-Username", "你的用户名"); connection.addRequestProperty("X-Password", "你的密码"); } catch (IOException e) { throw new WebServiceClientException("添加请求属性失败", e); } return true; } // 下面几个方法默认返回true即可 @Override public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException { // 空实现 } } }); return template; }
这个拦截器会在请求发出去之前,把和SoapUI一模一样的HTTP请求头加上,这样服务端就能正确拿到认证信息了。
情况2:认证信息放在SOAP Header里
如果Raw视图里的SOAP消息开头有类似<soapenv:Header><auth:Username>xxx</auth:Username>这样的内容,那说明需要把认证信息嵌入到SOAP Header里。这时候拦截器要这么写:
@Bean public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller) { WebServiceTemplate template = new WebServiceTemplate(marshaller); template.setInterceptors(new ClientInterceptor[]{ new ClientInterceptor() { @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { SoapMessage soapMessage = (SoapMessage) messageContext.getRequest(); SoapHeader header = soapMessage.getSoapHeader(); // 这里要对应服务端WSDL里定义的命名空间和元素名,从SoapUI的Raw里复制即可 QName authRootQName = new QName("http://你的服务命名空间", "Authentication"); SoapHeaderElement authElement = header.addHeaderElement(authRootQName); // 添加用户名子元素 QName userQName = new QName("http://你的服务命名空间", "Username"); authElement.addChildElement(userQName).setText("你的用户名"); // 添加密码子元素 QName passQName = new QName("http://你的服务命名空间", "Password"); authElement.addChildElement(passQName).setText("你的密码"); return true; } // 其他方法同上,返回true即可 @Override public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public boolean handleFault(MessageContext messageContext) throws WebServiceClientException { return true; } @Override public void afterCompletion(MessageContext messageContext, Exception ex) throws WebServiceClientException { // 空实现 } } }); return template; }
额外提示:用HttpClient作为请求发送器
如果你项目里用的是HttpComponentsMessageSender而不是默认的HttpUrlConnectionMessageSender,那拦截器里获取请求的方式要改一下:
@Bean public HttpComponentsMessageSender messageSender() { return new HttpComponentsMessageSender(); } @Bean public WebServiceTemplate webServiceTemplate(Jaxb2Marshaller marshaller, HttpComponentsMessageSender messageSender) { WebServiceTemplate template = new WebServiceTemplate(marshaller); template.setMessageSender(messageSender); template.setInterceptors(new ClientInterceptor[]{ new ClientInterceptor() { @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { HttpUriRequest request = (HttpUriRequest) messageContext.getProperty(HttpComponentsMessageSender.HTTP_URI_REQUEST); // 添加请求头 request.addHeader("X-Username", "你的用户名"); request.addHeader("X-Password", "你的密码"); return true; } // 其他方法同上 } }); return template; }
核心思路就是:让Spring发送的请求和SoapUI发送的请求完全一致。你可以把两者的Raw请求对比一下,看看差在哪里,调整代码补上缺失的部分,401的问题肯定能解决。
内容的提问来源于stack exchange,提问作者Junior Bezerra
相关产品推荐
相关产品推荐

