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

Spring WS调用无证书SOAP服务遇401未授权问题求助

解决Spring WS调用SOAP服务时的401 Unauthorized错误(通过请求属性传认证信息)

嘿,我之前也踩过这个坑!既然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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:13:39