Spring Boot中SOAP请求返回Unauthorized [401]时如何添加用户名密码实现有效请求
刚好之前在项目里踩过这个坑,给你分享两种最常用的解决方式,你可以根据目标SOAP服务的认证要求来选:
一、HTTP Basic 认证(大部分SOAP服务用这个)
很多SOAP服务会用HTTP层面的Basic认证,也就是把用户名密码编码后放在请求头里。这里有两种实现方式:
方式1:用HttpComponentsMessageSender配置(推荐)
这种方式依赖Apache HttpClient,支持更多HTTP特性,配置也更简洁:
首先确保你的pom.xml(或build.gradle)里有这些依赖:
<!-- Spring Web Services 核心依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> </dependency> <!-- Apache HttpClient,用于处理带认证的HTTP请求 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
然后写配置类,把用户名密码注入到WebServiceTemplate中:
@Configuration public class SoapClientConfig { @Value("${soap.service.username}") private String username; @Value("${soap.service.password}") private String password; @Value("${soap.service.url}") private String soapServiceUrl; @Bean public WebServiceTemplate webServiceTemplate() { WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); webServiceTemplate.setDefaultUri(soapServiceUrl); // 配置带Basic认证的HTTP发送器 HttpComponentsMessageSender messageSender = new HttpComponentsMessageSender(); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, // 匹配任意主机和端口,也可以指定具体的服务地址 new UsernamePasswordCredentials(username, password) ); CloseableHttpClient httpClient = HttpClientBuilder.create() .setDefaultCredentialsProvider(credentialsProvider) .build(); messageSender.setHttpClient(httpClient); webServiceTemplate.setMessageSender(messageSender); // 配置JAXB的Marshaller/Unmarshaller,用于序列化和反序列化SOAP消息 webServiceTemplate.setMarshaller(jaxb2Marshaller()); webServiceTemplate.setUnmarshaller(jaxb2Marshaller()); return webServiceTemplate; } @Bean public Jaxb2Marshaller jaxb2Marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); // 替换成你自己的SOAP实体类所在的包路径 marshaller.setContextPath("com.yourproject.soap.model"); return marshaller; } }
之后你直接注入WebServiceTemplate调用sendAndReceive方法,就会自动带上Basic认证头了。
方式2:自定义拦截器手动添加认证头
如果不想引入HttpClient依赖,也可以写一个拦截器手动构造Authorization头:
public class BasicAuthClientInterceptor implements ClientInterceptor { private final String authHeader; public BasicAuthClientInterceptor(String username, String password) { // 编码用户名密码生成Basic认证头 String auth = username + ":" + password; byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); this.authHeader = "Basic " + new String(encodedAuth); } @Override public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException { // 获取当前请求的HTTP连接 TransportContext transportContext = TransportContextHolder.getTransportContext(); HttpUrlConnection connection = (HttpUrlConnection) transportContext.getConnection(); // 添加Authorization头 connection.setRequestProperty("Authorization", authHeader); 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 { // 不需要额外清理,空实现即可 } }
然后在配置WebServiceTemplate的时候添加这个拦截器:
webServiceTemplate.setInterceptors(new ClientInterceptor[]{ new BasicAuthClientInterceptor(username, password) });
二、WS-Security UsernameToken 认证(针对要求SOAP头安全的服务)
有些严格的SOAP服务会要求把认证信息放在SOAP消息的Header里,而不是HTTP头。这时候需要用Spring WS的WS-Security支持:
首先添加WS-Security的依赖:
<dependency> <groupId>org.springframework.ws</groupId> <artifactId>spring-ws-security</artifactId> </dependency>
然后配置Wss4jSecurityInterceptor:
@Configuration public class SoapClientConfig { @Value("${soap.service.username}") private String username; @Value("${soap.service.password}") private String password; @Value("${soap.service.url}") private String soapServiceUrl; @Bean public WebServiceTemplate webServiceTemplate() { WebServiceTemplate webServiceTemplate = new WebServiceTemplate(); webServiceTemplate.setDefaultUri(soapServiceUrl); // 添加WS-Security拦截器 webServiceTemplate.setInterceptors(new ClientInterceptor[]{wss4jSecurityInterceptor()}); webServiceTemplate.setMarshaller(jaxb2Marshaller()); webServiceTemplate.setUnmarshaller(jaxb2Marshaller()); return webServiceTemplate; } @Bean public Wss4jSecurityInterceptor wss4jSecurityInterceptor() { Wss4jSecurityInterceptor interceptor = new Wss4jSecurityInterceptor(); // 指定要执行的安全动作:添加UsernameToken interceptor.setSecurementActions("UsernameToken"); // 设置认证用户名 interceptor.setSecurementUsername(username); // 设置认证密码 interceptor.setSecurementPassword(password); // 密码类型:PasswordText是明文,PasswordDigest是摘要,根据服务端要求改 interceptor.setSecurementPasswordType("PasswordText"); return interceptor; } @Bean public Jaxb2Marshaller jaxb2Marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath("com.yourproject.soap.model"); return marshaller; } }
这个配置会在你的SOAP请求Header里自动生成<wsse:UsernameToken>元素,包含用户名和密码,符合WS-Security标准。
最后提醒一下:先搞清楚目标SOAP服务到底用的是哪种认证方式——可以看服务的文档,或者抓包看正常请求的结构,再选对应的方案。
内容的提问来源于stack exchange,提问作者kbqhzh
相关产品推荐
相关产品推荐

