这里服务端开放了简单的SOAP的API,但是想获取数据时需要双向SSL以及WS-Security签名。
其中对应的xsd文件如下:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://spring.io/guides/gs-producing-web-service"
targetNamespace="http://spring.io/guides/gs-producing-web-service" elementFormDefault="qualified">
<xs:element name="getCountryRequest">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getCountryResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="country" type="tns:country"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="country">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="population" type="xs:int"/>
<xs:element name="capital" type="xs:string"/>
<xs:element name="currency" type="tns:currency"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="currency">
<xs:restriction base="xs:string">
<xs:enumeration value="GBP"/>
<xs:enumeration value="EUR"/>
<xs:enumeration value="PLN"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
对应的Maven如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.16.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version>3.0.8.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>2.5.0</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>${project.basedir}/src/main/resources/countries.xsd</source>
</sources>
</configuration>
</plugin>
</plugins>
</build>
</project>
将源码跑起来(文章最后有源码的打包下载地址)
从中可以看到,HTTPS以及开启
这里使用SoapUI来测试,获取其wsdl
会提示如下错误
下面将证书挂上!
完成后测试下:
可以看到连接成功,获取到了wsdl
客户端的配置
客户端中配置主要在SoapConfiguration.java中,首先是配置SSL双向认证:
private static final Resource KEYSTORE_LOCATION = new ClassPathResource("client.jks");
private static final String KEYSTORE_PASSWORD = "keystore";
...
@Bean
Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("io.spring.guides.gs_producing_web_service");
return marshaller;
}
@Bean
KeyStoreFactoryBean keyStore() {
KeyStoreFactoryBean factoryBean = new KeyStoreFactoryBean();
factoryBean.setLocation(KEYSTORE_LOCATION);
factoryBean.setPassword(KEYSTORE_PASSWORD);
return factoryBean;
}
@Bean
TrustManagersFactoryBean trustManagers(KeyStoreFactoryBean keyStore) {
TrustManagersFactoryBean factoryBean = new TrustManagersFactoryBean();
factoryBean.setKeyStore(keyStore.getObject());
return factoryBean;
}
@Bean
HttpsUrlConnectionMessageSender messageSender(
KeyStoreFactoryBean keyStore,
TrustManagersFactoryBean trustManagers
) throws Exception {
HttpsUrlConnectionMessageSender sender = new HttpsUrlConnectionMessageSender();
KeyManagersFactoryBean keyManagersFactoryBean = new KeyManagersFactoryBean();
keyManagersFactoryBean.setKeyStore(keyStore.getObject());
keyManagersFactoryBean.setPassword(KEYSTORE_PASSWORD);
keyManagersFactoryBean.afterPropertiesSet();
sender.setKeyManagers(keyManagersFactoryBean.getObject());
sender.setTrustManagers(trustManagers.getObject());
return sender;
}
双向SSL认证配置同样也需要在服务端进行设置。在高度安全的环境中客户端都需要有自己的证书,在与服务器通信之前都需要验证这些证书合不合法。
客户端的keystore存放的路径为:src/mian/resources/client.jks。这个jks中包含了客户端的私钥和公钥。使用spring-ws-security配置代替HttpsUrlConnectionMessageSender,使用这种方式可以实现SOAP的双向SSL认证。
下面是配置WS-Security:
@Bean
CryptoFactoryBean cryptoFactoryBean() throws IOException {
CryptoFactoryBean cryptoFactoryBean = new CryptoFactoryBean();
cryptoFactoryBean.setKeyStoreLocation(KEYSTORE_LOCATION);
cryptoFactoryBean.setKeyStorePassword(KEYSTORE_PASSWORD);
return cryptoFactoryBean;
}
@Bean
Wss4jSecurityInterceptor securityInterceptor(CryptoFactoryBean cryptoFactoryBean) throws Exception {
Wss4jSecurityInterceptor securityInterceptor = new Wss4jSecurityInterceptor();
securityInterceptor.setSecurementActions("Signature");
securityInterceptor.setSecurementUsername(KEY_ALIAS);
securityInterceptor.setSecurementPassword(KEYSTORE_PASSWORD);
securityInterceptor.setSecurementSignatureKeyIdentifier("DirectReference");
securityInterceptor.setSecurementSignatureAlgorithm(WSS4JConstants.RSA_SHA1);
securityInterceptor.setSecurementSignatureDigestAlgorithm(WSS4JConstants.SHA1);
securityInterceptor.setSecurementSignatureCrypto(cryptoFactoryBean.getObject());
return securityInterceptor;
}
这里需要为WSSJ4提供客户端的keystore及password。配置SecurityInterceptor,这个拦截器将每个SOAP消息都带上签名。
最后在GetCountryReponseClient中使用双向SSL及WS-Security:
@Bean
CountryClient countryClient(
Jaxb2Marshaller marshaller,
HttpsUrlConnectionMessageSender messageSender,
Wss4jSecurityInterceptor securityInterceptor
) {
CountryClient countryClient = new CountryClient();
countryClient.setInterceptors(new ClientInterceptor[]{securityInterceptor});
countryClient.setMessageSender(messageSender);
countryClient.setMarshaller(marshaller);
countryClient.setUnmarshaller(marshaller);
return countryClient;
}
下面是测试下客户端,看看连接是否正常以及能否调用服务:
@SpringBootTest
public class CountryClientIntegrationTest {
@Autowired
CountryClient countryClient;
@Test
void shouldDownloadCountry() {
// given
String countryName = "Poland";
// when
Country country = countryClient.getCountry(countryName).getCountry();
// then
Country expectedCountry = new Country();
expectedCountry.setName("Poland");
expectedCountry.setCapital("Warsaw");
expectedCountry.setCurrency(Currency.PLN);
expectedCountry.setPopulation(38186860);
assertThat(country.getName()).isEqualTo("Poland");
assertThat(country.getCapital()).isEqualTo("Warsaw");
assertThat(country.getCurrency()).isEqualTo(Currency.PLN);
assertThat(country.getPopulation()).isEqualTo(38186860);
}
}
服务端下载地址:
https://github.com/fengfanchen/Java/tree/master/SSLWebService
客户端下载地址:
https://github.com/fengfanchen/Java/tree/master/SSLWebServiceClient