You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 {
    }
}

问题分析

  1. Basic Auth格式错误:标准Basic Auth仅支持username:password的Base64编码,代码中加入domain的格式不符合RFC规范,导致服务端无法解析认证信息,触发500错误。
  2. ClientInterceptor实现缺陷:
    • 初始版本直接设置messageContext属性,无法转化为HTTP请求头;
    • 修改后的版本未确保WebServiceConnection类型匹配,且遗漏返回true(返回false会直接中断请求)。
  3. Content-Type冲突:Spring WebServiceTemplate会根据SOAP版本自动设置正确的Content-Type(SOAP 1.1为text/xml,SOAP 1.2为application/soap+xml),手动设置application/xml可能与服务端期望格式不匹配,引发400错误。
  4. 配置冲突:同时设置MessageSender和Interceptor可能存在执行顺序或覆盖问题,且Interceptor未正确注入到WebServiceTemplate。

解决方案

方案1:修复自定义MessageSender(推荐)

  1. 修正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);
    }
}
  1. 更新配置类,正确初始化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实现

  1. 完善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 {
    }
}
  1. 在配置类中添加认证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;
}

额外排查步骤

  1. 启用SOAP日志:在application.properties中添加配置,查看实际发送的请求头和SOAP报文,确认认证头是否正确:
logging.level.org.springframework.ws.client.MessageTracing.sent=TRACE
logging.level.org.springframework.ws.client.MessageTracing.received=TRACE
  1. 验证服务端认证要求:若服务端需要域账号格式(如domain\username),则将用户名设置为domain\username,再与密码拼接后Base64编码。
  2. 基础请求测试:使用Postman或SoapUI直接调用SOAP API,携带正确的Basic Auth头,确认服务端是否正常响应,排除服务端自身问题。

内容的提问来源于stack exchange,提问作者BALA AP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 20:31:35