Spring WebServiceTemplate调用SOAP API认证失败求助
Spring WebServiceTemplate SOAP API认证问题排查与解决
问题背景
在Spring Boot中基于WebServiceGatewaySupport使用WebServiceTemplate调用SOAP API时,遭遇认证相关错误,尝试两种认证方式均未解决:
- 场景1:通过
MessageSender传递认证信息和Content-Type时,返回400 Bad Request;注释Content-Type后返回500 Internal Server Error - 场景2:通过
ClientInterceptor传递信息时,始终返回500 Internal Server Error,且认证信息未生效 - 移除认证代码后,错误结果无变化
现有代码片段
1. WebServiceTemplate配置类
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller, ClientInterceptor clientInterceptor) { WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); //SOAP URL webServiceTemplate.setDefaultUri("http://host/Services.asmx"); //Auth ---It seems issue is here only????? need to check webServiceTemplate.setMessageSender(new Authentication()); webServiceTemplate.setMarshaller(marshaller); webServiceTemplate.setUnmarshaller(marshaller); webServiceTemplate.afterPropertiesSet(); webServiceTemplate.setCheckConnectionForFault(true); webServiceTemplate.setInterceptors((ClientInterceptor[]) Arrays.asList(createLoggingInterceptor()).toArray()); return webServiceTemplate; }
2. SOAP客户端调用类
public class TicketClient extends WebServiceGatewaySupport { public String getTicket(Ticket req) { System.out.println("test inside webservice support1"); AcquireTicketResponse response = (AcquireTicketResponse) getWebServiceTemplate().marshalSendAndReceive(req); // 省略后续处理逻辑 } }
3. 自定义MessageSender认证类
public class Authentication extends HttpUrlConnectionMessageSender { @Override protected void prepareConnection(HttpURLConnection connection) { String userpassword = username+":"+password+":"+domain; String encoded = Base64.getEncoder().withoutPadding().encodeToString(userpassword.getBytes(StandardCharsets.UTF_8)); connection.setRequestProperty("Authorization", "Basic "+encoded); connection.setRequestProperty("Content-Type", "application/xml"); super.prepareConnection(connection); } }
4. 尝试的ClientInterceptor认证实现
public class SoapLoggingInterceptor implements ClientInterceptor { @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { String username="test"; String password="test"; String domain = "@test"; String userpassword = username+":"+password+domain; byte[] userpasswordBytes = (username+":"+password).getBytes(StandardCharsets.UTF_8); String encoded = Base64.getEncoder().encodeToString(userpasswordBytes); try { TransportContext context = TransportContextHolder.getTransportContext(); WebServiceConnection conn = context.getConnection(); ((HeadersAwareSenderWebServiceConnection) conn).addRequestHeader("Authorization", "Basic " + encoded); } catch (IOException e) { throw new WebServiceIOException(e.getMessage(), e); } // 原代码遗漏返回值,会导致请求中断 return 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 { } }
问题分析
- Basic Auth格式错误:标准Basic Auth仅支持
username:password的Base64编码,代码中加入domain的格式不符合RFC规范,导致服务端无法解析认证信息,触发500错误。 - ClientInterceptor实现缺陷:
- 初始版本直接设置
messageContext属性,无法转化为HTTP请求头; - 修改后的版本未确保
WebServiceConnection类型匹配,且遗漏返回true(返回false会直接中断请求)。
- 初始版本直接设置
- Content-Type冲突:Spring WebServiceTemplate会根据SOAP版本自动设置正确的Content-Type(SOAP 1.1为
text/xml,SOAP 1.2为application/soap+xml),手动设置application/xml可能与服务端期望格式不匹配,引发400错误。 - 配置冲突:同时设置
MessageSender和Interceptor可能存在执行顺序或覆盖问题,且Interceptor未正确注入到WebServiceTemplate。
解决方案
方案1:修复自定义MessageSender(推荐)
- 修正Basic Auth编码格式,移除多余的domain拼接:
public class Authentication extends HttpUrlConnectionMessageSender { // 建议通过构造函数注入认证信息,避免硬编码 private final String username; private final String password; public Authentication(String username, String password) { this.username = username; this.password = password; } @Override protected void prepareConnection(HttpURLConnection connection) { // 标准Basic Auth格式:仅username:password String authCredentials = username + ":" + password; String encoded = Base64.getEncoder().encodeToString(authCredentials.getBytes(StandardCharsets.UTF_8)); connection.setRequestProperty("Authorization", "Basic " + encoded); // 移除手动设置的Content-Type,使用框架默认值 super.prepareConnection(connection); } }
- 更新配置类,正确初始化MessageSender:
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller) { WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); webServiceTemplate.setDefaultUri("http://host/Services.asmx"); // 注入正确的认证信息 webServiceTemplate.setMessageSender(new Authentication("test", "test")); webServiceTemplate.setMarshaller(marshaller); webServiceTemplate.setUnmarshaller(marshaller); webServiceTemplate.afterPropertiesSet(); webServiceTemplate.setCheckConnectionForFault(true); // 按需添加日志拦截器 webServiceTemplate.setInterceptors(new ClientInterceptor[]{createLoggingInterceptor()}); return webServiceTemplate; }
方案2:修复ClientInterceptor实现
- 完善Interceptor逻辑,确保正确设置请求头:
public class SoapAuthInterceptor implements ClientInterceptor { private final String username; private final String password; public SoapAuthInterceptor(String username, String password) { this.username = username; this.password = password; } @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { String authCredentials = username + ":" + password; String encoded = Base64.getEncoder().encodeToString(authCredentials.getBytes(StandardCharsets.UTF_8)); try { TransportContext context = TransportContextHolder.getTransportContext(); WebServiceConnection conn = context.getConnection(); // 类型校验避免转换异常 if (conn instanceof HeadersAwareSenderWebServiceConnection) { ((HeadersAwareSenderWebServiceConnection) conn).addRequestHeader("Authorization", "Basic " + encoded); } } catch (IOException e) { throw new WebServiceIOException("Failed to set auth header", 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 { } }
- 在配置类中添加认证Interceptor:
public WebServiceTemplate createWebServiceTemplate(Jaxb2Marshaller marshaller) { WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); webServiceTemplate.setDefaultUri("http://host/Services.asmx"); webServiceTemplate.setMarshaller(marshaller); webServiceTemplate.setUnmarshaller(marshaller); webServiceTemplate.afterPropertiesSet(); webServiceTemplate.setCheckConnectionForFault(true); // 顺序:认证拦截器优先执行,再执行日志拦截器 webServiceTemplate.setInterceptors(new ClientInterceptor[]{ new SoapAuthInterceptor("test", "test"), createLoggingInterceptor() }); return webServiceTemplate; }
额外排查步骤
- 启用SOAP日志:在
application.properties中添加配置,查看实际发送的请求头和SOAP报文,确认认证头是否正确:
logging.level.org.springframework.ws.client.MessageTracing.sent=TRACE logging.level.org.springframework.ws.client.MessageTracing.received=TRACE
- 验证服务端认证要求:若服务端需要域账号格式(如
domain\username),则将用户名设置为domain\username,再与密码拼接后Base64编码。 - 基础请求测试:使用Postman或SoapUI直接调用SOAP API,携带正确的Basic Auth头,确认服务端是否正常响应,排除服务端自身问题。
内容的提问来源于stack exchange,提问作者BALA AP
相关产品推荐
相关产品推荐

