今天項目上面需要做一個CXF+ws-security和HttpURLConnection調用第三方的webService進行通路,這裡我主要做用戶端通路,伺服器端和用戶端完整請看原創。
用戶端需要jar(純java調用):
asm-3.3.jar
commons-logging.jar
cxf-2.7.18.jar
cxf-api-2.7.18.jar
cxf-rt-frontend-jaxws-2.7.8.jar
cxf-rt-ws-security-2.7.8.jar
javapns-jdk16-2.2.1.jar
neethi-3.0.3.jar
stax2-api-3.1.4.jar
woodstox-core-asl-4.4.1.jar
wsdl4j-1.6.3.jar
wss4j-1.6.19.jar
xmlschema-core-2.1.0.jar
xmlsec-1.5.8.jar
---------------------
maven依賴:
<!-- webService用戶端start -->
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-api</artifactId>
<version>2.7.18</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf</artifactId>
<version>2.7.18</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-ws-security</artifactId>
<version>2.7.8</version>
</dependency>
<dependency>
<groupId>com.github.fernandospr</groupId>
<artifactId>javapns-jdk16</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.neethi</groupId>
<artifactId>neethi</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>stax2-api</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>wsdl4j</groupId>
<artifactId>wsdl4j</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.ws.security</groupId>
<artifactId>wss4j</artifactId>
<version>1.6.19</version>
</dependency>
<dependency>
<groupId>org.apache.ws.xmlschema</groupId>
<artifactId>xmlschema-core</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1-beta-6</version>
</dependency>
<!-- webService用戶端end -->
純java調用webService包含兩步:
一、建立驗證輔助類,如本實列的PasswordHandler,用于向用戶端傳遞密碼。
二、建立調用類,如本實列的WsClientUtil,進行調用。
說明:
同服務端一樣,PasswordHandler也是實作了CallbackHandler接口,與伺服器端不同的是用戶端該類的用途隻是為了設定密碼,不許要做任何驗證。詳情見代碼。
一、方法updateMobileInfo和getMobileInfo是使用cxf+ws-security做的webService安全請求驗證。
二、方法getDirectoryEntryByAccount和resetAccountPassword是使用HttpURLConnection做的webService請求。
package com.hxzq.s0026.my168.util.webservice;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.dom4j.Attribute;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.hxzq.s0026.my168.util.LogUtil;
import com.thinkive.base.config.Configuration;
import com.thinkive.base.exception.CommonException;
import com.thinkive.base.util.StringHelper;
/**
*
* @說明:調用第三方接口,采用CXF +ws-security 實作webservice用戶端調用
* @類型名稱:WsClientUtil
* @建立者: 敬進
* @建立時間: 2018年10月30日 上午10:11:26
* @修改者: 敬進
* @修改時間: 2018年10月30日 上午10:11:26
*/
public class WsClientUtil {
private static JaxWsDynamicClientFactory dcf = null;
private static WSS4JOutInterceptor wssOut = null;
private static String url_90 = null;
private static String url_91 = null;
private static String namespace = null;
static {
// 擷取配置檔案資訊
url_90 = Configuration.getString("ws0002.url_90");
url_91 = Configuration.getString("ws0001.url_91");
namespace = Configuration.getString("ws0002.namespace");
String account = Configuration.getString("ws0001.account");
// 這個是用cxf 用戶端通路cxf部署的webservice服務
// 千萬記住,通路cxf的webservice必須加上namespace ,否則通不過
dcf = JaxWsDynamicClientFactory.newInstance();
Map<String, Object> props = new HashMap<String, Object>();
props.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// 密碼類型 明文:PasswordText密文:PasswordDigest
props.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// 使用者名
props.put(WSHandlerConstants.USER, account);
// 将PasswordHandler 的類名傳遞給伺服器,相當于傳遞了密碼給伺服器
props.put(WSHandlerConstants.PW_CALLBACK_CLASS, PasswordHandler.class.getName());
props.put(WSHandlerConstants.MUST_UNDERSTAND, "0");
wssOut = new WSS4JOutInterceptor(props);
}
/**
*
* @說明: updateMobileInfo修改使用者手機号方法
* @方法名稱: updateMobileInfo
* @參數 @param loginName 使用者名
* @參數 @param phone 電話号碼
* @參數 @return
* @傳回類型 String
* @建立者: 敬進
* @建立時間: 2018年10月30日 下午2:07:44
* @修改者: 敬進
* @修改時間: 2018年10月30日 下午2:07:44
*/
public static String updateMobileInfo(String loginName, String phone) {
try {
// 建立一個用戶端
LogUtil.info(url_91);
Client client = dcf.createClient(url_91);
// 添加通路參數
LogUtil.info("------開始------");
client.getOutInterceptors().add(wssOut);
LogUtil.info("------添加通路參數------");
// 執行方法
Object[] objects = client.invoke("updateMobileInfo", loginName, phone);
LogUtil.info("------執行方法------");
return (String) objects[0];
} catch (Exception e) {
// TODO: handle exception
LogUtil.error(e.getMessage());
throw new CommonException(1, "修改使用者手機号失敗!");
}
}
/**
*
* @說明:getMobileInfo擷取使用者聯系資訊方法
* @方法名稱: getMobileInfo
* @參數 @param loginName 使用者名
* @參數 @return
* @傳回類型 String
* @建立者: 敬進
* @建立時間: 2018年10月30日 下午2:10:34
* @修改者: 敬進
* @修改時間: 2018年10月30日 下午2:10:34
*/
public static String getMobileInfo(String loginName) {
try {
Client client = dcf.createClient(url_91);
client.getOutInterceptors().add(wssOut);
Object[] objects = client.invoke("getMobileInfo", loginName);
return (String) objects[0];
} catch (Exception e) {
// TODO: handle exception
LogUtil.error(e.getMessage());
throw new CommonException(1, "擷取使用者聯系資訊失敗!");
}
}
/**
* 、
* @說明: 擷取密碼過期時間方法
* @方法名稱: getDirectoryEntryByAccount
* @參數 @param loginName 登入名
* @參數 @param password 密碼
* @參數 @return
* @傳回類型 Map<String,Object> key:Result、OperationMessage、BizData
* @建立者: 敬進
* @建立時間: 2018年11月1日 上午10:29:09
* @修改者: 敬進
* @修改時間: 2018年11月1日 上午10:29:09
*/
public static Map<String, Object> getDirectoryEntryByAccount(String loginName, String password) {
StringBuilder sb = new StringBuilder("");
sb.append(
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:oa=\"https://oa.hx168.com.cn/\">")
.append("<soapenv:Header/>").append("<soapenv:Body>")
.append("<oa:GetDirectoryEntryByAccount>")
.append("<oa:adAccount>").append(loginName).append("</oa:adAccount>")
.append("<oa:password>").append(password).append("</oa:password>")
.append("</oa:GetDirectoryEntryByAccount>")
.append("</soapenv:Body>").append("</soapenv:Envelope>");
String dataXml = sb.toString();
String soapAction = namespace + "GetDirectoryEntryByAccount";
String resultXml = httpConnUtil(dataXml, soapAction);
// xml解析
Map<String, Object> resultMap=readStringXml(resultXml);
return resultMap;
}
/**
*
* @說明: 修改使用者密碼方法
* @方法名稱: resetAccountPassword
* @參數 @param loginName 登入名
* @參數 @param oldPassword 舊密碼
* @參數 @param newPassword 新密碼
* @參數 @return
* @傳回類型 Map<String,Object> key:Result、OperationMessage、BizData
* @建立者: 敬進
* @建立時間: 2018年10月30日 下午2:14:38
* @修改者: 敬進
* @修改時間: 2018年10月30日 下午2:14:38
*/
public static Map<String, Object> resetAccountPassword(String loginName, String oldPassword, String newPassword) {
StringBuilder sb = new StringBuilder("");
sb.append(
"<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:oa=\"https://oa.hx168.com.cn/\">")
.append("<soapenv:Header/>").append("<soapenv:Body>")
.append("<oa:ResetAccountPassword>")
.append("<oa:adAccount>").append(loginName).append("</oa:adAccount>")
.append("<oa:oldPassword>").append(oldPassword).append("</oa:oldPassword>")
.append("<oa:newPassword>").append(newPassword).append("</oa:newPassword>")
.append("</oa:ResetAccountPassword>")
.append("</soapenv:Body>").append("</soapenv:Envelope>");
String dataXml = sb.toString();
String soapAction = namespace + "ResetAccountPassword";
String resultXml = httpConnUtil(dataXml, soapAction);
// xml解析
Map<String, Object> resultMap=readStringXml(resultXml);
return resultMap;
}
/**
*
* @說明: http請求處理webservice
* @方法名稱: httpConnUtil
* @參數 @param dataXml xml入參
* @參數 @param soapAction 命名空間加方法名
* @參數 @return
* @傳回類型 map
* @建立者: 敬進
* @建立時間: 2018年10月30日 下午2:12:24
* @修改者: 敬進
* @修改時間: 2018年10月30日 下午2:12:24
*/
private static String httpConnUtil(String dataXml, String soapAction) {
String contentType = "text/xml; charset=utf-8";
HttpURLConnection httpConn = null;
OutputStream out = null;
try {
httpConn = (HttpURLConnection) new URL(url_90).openConnection();
httpConn.setRequestProperty("Content-Type", contentType);
if (null != soapAction) {
httpConn.setRequestProperty("SOAPAction", soapAction);
}
httpConn.setRequestMethod("POST");
httpConn.setDoOutput(true);
httpConn.setDoInput(true);
httpConn.connect();
out = httpConn.getOutputStream();// 擷取輸出流對象
httpConn.getOutputStream().write(dataXml.getBytes("UTF-8"));// 将要送出伺服器的SOAP請求字元流寫入輸出流
out.flush();
out.close();
int code = httpConn.getResponseCode();// 用來擷取伺服器相應狀态
String tempString = null;
StringBuffer sb = new StringBuffer();
if (code == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "UTF-8"));
while ((tempString = reader.readLine()) != null) {
sb.append(tempString);
}
if (null != reader) {
reader.close();
}
} else {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getErrorStream(), "UTF-8"));
// 一次讀入一行,直到讀入null為檔案結束
while ((tempString = reader.readLine()) != null) {
sb.append(tempString);
}
if (null != reader) {
reader.close();
}
}
return sb.toString();
} catch (Exception e) {
// TODO: handle exception
LogUtil.error(e.getMessage());
throw new CommonException(1, "員工密碼修改失敗!");
}
}
/**
*
* @說明: dom4j解析xml
* @方法名稱: readStringXml
* @參數 @param xml
* @參數 @return
* @傳回類型 Map<String,Object>
* @建立者: 敬進
* @建立時間: 2018年11月1日 上午8:43:25
* @修改者: 敬進
* @修改時間: 2018年11月1日 上午8:43:25
*/
@SuppressWarnings("unchecked")
private static Map<String, Object> readStringXml(String xml) {
org.dom4j.Document doc = null;
Map<String, Object> resultMap=null;
try {
doc = DocumentHelper.parseText(xml);
Element rootElt = doc.getRootElement();
//擷取根節點下body子節點
List<Element> elements = rootElt.selectNodes("soap:Body");
if (elements.size()!=0) {
for (Element element : elements) {
Element parent=element.getParent();
resultMap=new HashMap<String,Object>();
arrayNodes(resultMap, element, rootElt);
//如果存在多個item資料才調用這個
List<Attribute> attributes = parent.attributes();
if (attributes.size()>0) {
for (Attribute attribute : attributes) {
//将屬性名(key)和屬性值(value)添加到map對象中去
resultMap.put(attribute.getName(), attribute.getValue());
}
}else{
LogUtil.info("該節點沒有任何屬性節點!");
}
}
}else{
LogUtil.info("xPath(DOM樹路徑)出現錯誤!");
}
} catch (DocumentException e) {
// TODO Auto-generated catch block
LogUtil.info(e.getMessage());
throw new CommonException(1,"DOM解析失敗!");
}
return resultMap;
}
//一般類型的解析
private static void arrayNodes(Map<String, Object> nodeMap, Element node, Element root) {
LogUtil.info("----------------------------");
// 目前節點的名稱、文本内容和屬性
LogUtil.info("目前節點名稱:" + node.getName());// 目前節點名稱
if (!(node.getTextTrim().equals(""))) {
LogUtil.info("目前節點的内容:" + node.getTextTrim());// 目前節點名稱
}
String nodeName = node.getName();
String nodeValue = node.getTextTrim();
if (StringHelper.isNotEmpty(nodeValue)) {
nodeMap.put(nodeName, nodeValue);
}
// 目前節點下面子節點疊代器
@SuppressWarnings("unchecked")
Iterator<Element> it = node.elementIterator();
// 周遊
while (it.hasNext()) {
// 擷取某個子節點對象
Element e = it.next();
// 對子節點進行周遊
arrayNodes(nodeMap, e, root);
}
}
}
在做cxf+ws-security時需要傳輸一個password
package com.hxzq.s0026.my168.util.webservice;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
import com.thinkive.base.config.Configuration;
/**
*
* @說明:webService驗證輔助類,用于向ws-security發送密碼
* @類型名稱:PasswordHandler
* @建立者: 敬進
* @建立時間: 2018年10月30日 下午1:11:21
* @修改者: 敬進
* @修改時間: 2018年10月30日 下午1:11:21
*/
public class PasswordHandler implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
// TODO Auto-generated method stub
String password=Configuration.getString("ws0001.password");
for (int i = 0; i < callbacks.length; i++) {
WSPasswordCallback pc=(WSPasswordCallback) callbacks[i];
pc.setPassword(password);
}
}
}
其中namespace是wsdl中的命名空間,url_90和url_91是webService接口。
使用HttpURLConnection做webService用戶端時,需要拼接發送請求的參數,然後使用dom4j解析傳回的參數,如
調用getDirectoryEntryByAccount方法,拼接參數類似。
使用方法readStringXml解析傳回的參數。
cxf+ws-secur原創ity
webService調用方法還有axis和axis2
一般遇到的錯誤