前几天,项目需要调用第三放的https的Webservice接口,说起来都是一把辛酸泪!!!
简单的说下实现以及踩过的坑!!!!!!!!!!!!!!!!
第一步:项目为maven项目,使用axis2需要导入相应jar包,下面是所需要的maven依赖:
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2</artifactId>
<version>1.6.2</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.6.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-kernel</artifactId>
<version>1.6.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-http</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-transport-local</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-adb</artifactId>
<version>1.6.2</version>
<scope>compile</scope>
</dependency>
第二步:编写接口调用逻辑:
第一种为匿名参数的格式,参数类似于<args0></args0>之类的,不需要设置参数名称:
好处:不用设置参数名称,返回值直接可以使用,下面是soapUI调用接口的返回值,第一种方式返回值为<return><return/>标签里面的值,我们可以直接使用。
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:ApplyKeyResponse xmlns:ns2="http://webservice.samples.pd.les.cn/">
<return><![CDATA[<?xml version="1.0" encoding="utf-8"?><root><state>false</state><result>接口调用异常:IP地址错误</result></root>]]></return>
</ns2:ApplyKeyResponse>
</soap:Body>
</soap:Envelope>
/**
* 调用Webservice公共接口 你们参数
* @param corPlistByCorpName 接口调用方法名
* @param xml 字符串类型的xml
* @param webServiceUrl:接口地址不要添加?wsdl
* @return
*/
@SuppressWarnings("rawtypes")
public String useWebService(String corPlistByCorpName, String xml) {
String result = "";
try {
Object[] requestParam = new Object[] { xml };
RPCServiceClient serviceClient = new RPCServiceClient();
// 指定调用WebService的URL
EndpointReference targetEPR = new EndpointReference(webServiceUrl);
Options options = serviceClient.getOptions();
options.setTo(targetEPR);
options.setTimeOutInMilliSeconds(30000);
options.setProperty(HTTPConstants.CHUNKED, false);
serviceClient.setOptions(options);
// 指定方法返回值的数据类型的Class对象
Class[] responseParam = new Class[] { String.class };
// 指定要调用的getGreeting方法及WSDL文件的命名空间
QName requestMethod = new QName(webServiceUrl, corPlistByCorpName);
// 调用方法并输出该方法的返回值
if (logger.isDebugEnabled()) {
logger.debug("报送XML:" + xml);
}
result = (String) serviceClient.invokeBlocking(requestMethod, requestParam, responseParam)[0];
if (logger.isInfoEnabled()) {
logger.info("返回结果:" + result);
}
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error("报送异常:" + e.fillInStackTrace());
}
e.printStackTrace();
}
return result;
}
第二种,需要对参数名称进行设置,我使用的是这个,因为我调用的接口需要用到参数名称
跟第一种的区别除了参数以外,我们还需要对返回值进行处理,OMElement results,返回值是 <soap:Body></soap:Body>里面的值,我们不可以直接使用。我用的是字符串截取获取我需要的节点里面的值,方法详见下面代码。
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:ApplyKeyResponse xmlns:ns2="http://webservice.samples.pd.les.cn/">
<return><![CDATA[<?xml version="1.0" encoding="utf-8"?><root><state>false</state><result>接口调用异常:IP地址错误</result></root>]]></return>
</ns2:ApplyKeyResponse>
</soap:Body>
</soap:Envelope>
/**
* 调用Webservice公共接口
* @param corPlistByCorpName
* @param requeststr
* @param webServiceUrl:接口地址不要添加?wsdl
* @return
*/
@SuppressWarnings("rawtypes")
public String useWebService(String corPlistByCorpName, String requeststr,String webServiceUrl) {
String result = "";
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
SSLContext.setDefault(sslContext);
RPCServiceClient serviceClient = new RPCServiceClient();
// 指定调用WebService的URL
EndpointReference targetEPR = new EndpointReference(webServiceUrl);
Options options = serviceClient.getOptions();
options.setTo(targetEPR);
options.setTimeOutInMilliSeconds(30000);
options.setProperty(HTTPConstants.CHUNKED, false);//不限制
options.setProperty(org.apache.axis2.Constants.Configuration.DISABLE_SOAP_ACTION, true); //不设置的话会报错,忘记错误是什么了
options.setTransportInProtocol(Constants.TRANSPORT_HTTPS);//没起到什么作用
options.setAction(serviceNamespace+corPlistByCorpName);//命名空间+方法名
serviceClient.setOptions(options);
OMFactory fac = OMAbstractFactory.getOMFactory();
// 这个和qname差不多,设置命名空间
OMNamespace omNs = fac.createOMNamespace(serviceNamespace, corPlistByCorpName);
OMElement data = fac.createOMElement(corPlistByCorpName, omNs);
if(StringUtils.isBlank(requeststr)){
// 对应参数的节点
String[] strs = new String[] { "token" };
// 参数值
String[] val = new String[] { token };
for (int i = 0; i < strs.length; i++) {
OMElement inner = fac.createOMElement(strs[i], omNs);
inner.setText(val[i]);
data.addChild(inner);
}
}else{
// 对应参数的节点
String[] strs = new String[] { "token","requeststr" };
// 参数值
String[] val = new String[] { token,requeststr };
for (int i = 0; i < strs.length; i++) {
OMElement inner = fac.createOMElement(strs[i], omNs);
inner.setText(val[i]);
data.addChild(inner);
}
}
// 发送数据,返回结果
OMElement results = serviceClient.sendReceive(data);
if(results!=null){
result = results.toString();
System.out.println("返回结果results==:" + results.toString());
}else{
System.out.println("results==null");
}
} catch (Exception e) {
if (logger.isErrorEnabled()) {
logger.error("报送异常:" + e.fillInStackTrace());
}
e.printStackTrace();
}
return result;
}
/**
* 获取指定标签中的内容 CDATA
* @param xml 传入的xml字符串
*/
public String getFieldListByCDATA(String xml) {
String data = "";
//正则表达式
Pattern pattern = Pattern.compile("\\<\\!\\[CDATA\\[(?<text>[^\\]]*)\\]\\]\\>");
Matcher m = pattern.matcher(xml);
//匹配的有多个
List<String> fieldList = new ArrayList<>();
while (m.find()) {
if (StringUtils.isNotEmpty(m.group(1).trim())) {
data=m.group(1).trim();
}
}
return data;
}
/**
* 获取指定标签中的内容
* @param xml 传入的xml字符串
* @param label 指定的标签
*/
public String getFieldListByRegex(String xml, String label) {
String data = "";
//正则表达式
String regex = "<" + label + ">(.*?)</" + label + ">";
Pattern pattern = Pattern.compile(regex);
Matcher m = pattern.matcher(xml);
//匹配的有多个
List<String> fieldList = new ArrayList<>();
while (m.find()) {
if (StringUtils.isNotEmpty(m.group(1).trim())) {
fieldList.add(m.group(1).trim());
}
}
if(fieldList.size()>0){
data = fieldList.get(0);
}
return data;
}
第三步:接口调用过程中遇到的坑:
第一个问题:网上都说是更换TLS版本,我JKD用的是1.7的,默认版本是TLS,也就是1.0
org.apache.axis2.AxisFault: Remote host closed connection during handshake
at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430)
at org.apache.axis2.transport.http.AxisRequestEntity.writeRequest(AxisRequestEntity.java:98)
at org.apache.commons.httpclient.methods.EntityEnclosingMethod.writeRequestBody(EntityEnclosingMethod.java:499)
at org.apache.commons.httpclient.HttpMethodBase.writeRequest(HttpMethodBase.java:2114)...
Caused by: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:953)
... 96 moreCaused by: java.io.EOFException: SSL peer shut down incorrectly
at sun.security.ssl.InputRecord.read(InputRecord.java:482)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:934)
... 103 more
针对这个问题,我百度了好久,试了很多种方式,跳过,或者是在全局设置,但是都没有起作用,
https://blog.csdn.net/xtj332/article/details/52228351/ 跳过验证,我的是没有作用,可能是不会用吧
System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,SSLv3"); 全局设置,我的也没有作用
试了很多方法都不行,我一度以为我的方向是错的,难道是证书问题?直到看到了一个贴子,设置soapui属性,可以访问https接口,我试着设置了下
在SoapUI-5.2.1.vmoptions 文件中加入:-Dsoapui.https.protocols=TLSv1.1,TLSv1.2
果然,接口可以调用了,这就证明我的方向没有错,既然跳不过,那就设置吧,疯狂百度后,
最后看到了一个帖子,试了下,然后就可以了:
解决方案:在接口发送请求前,调用以下代码:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, null, null);
SSLContext.setDefault(sslContext);
ok,第一个问题解决,我中间怀疑是证书问题,事实证明是刚开始想的没错,先握手成功,才会去进行证书校验。
第二个问题:证书问题,也是试了好多绕过证书的方法,但是都没有成功,可能是太菜了吧,没办法,折腾证书吧!!!
报错信息如下:
sun.security.validator.ValidatorException: PKIX path building failed:
以前没弄过证书:就去百度:
第一种方式:用java代码生成:
java生成https安全证书,解决httpClient访问https出错
编译:javac InstallCert.java (文件去资源里面去下载)
运行:java InstallCert 要访问的网址(域名/IP+端口)
结果:Enter certificate to add to trusted keystore or 'q' to quit: [1]
输入1确认生成jssecacerts文件,
将证书copy到$JAVA_HOME/jre/lib/security目录下
我的java是7的,默认的TLS是1.0版本的,服务端不是1.0版本的话,回报错(也就是TSL版本),所以我这种方式行不通,我就使用了第二种方式:
第二种方式:在浏览器下载证书,可以忽略证书问题(过期什么的),
格式务必选择正确,要不不能用!!!!
接下来,使用 keytool 命令导入证书,进入到 JDK 下 jre 下 lib 下的 security 目录,比如我的是 C:\cs-softwares\Java\jdk1.8.0_111\jre\lib\security,然后运行命令 keytool -import -alias aliyun-maven -keystore cacerts -file C:\cs-softwares\aliyun-maven.cer ,如下,证书指纹(证书密钥):changeit
-alias :表示指定证书别名
-file :指定证书文件
输入 Y 表示确认:
查看证书,证书密钥同样是 changeit :
keytool -list -keystore cacerts -alias aliyun-maven
删除证书,证书密钥同样是 changeit:
keytool -delete -alias aliyun-maven -keystore cacerts
到此,再次运行,接口访问成功!!!!!!!搞定
这是带JDK里面设置,我们将其设置在项目中,然后在启动的时候调用下就可以了:
@PostConstruct private void init() throws Exception { String trustStore = ResourceUtils.getFile(trustStorePath).getAbsolutePath();//获取证书在项目中存放的路径 System.setProperty("javax.net.ssl.trustStore", trustStore);//设置地址 System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);//设置密码 默认为changeit }
ok,再次运行,接口调用成功!!!!!!!!!!!!!!!