【WebService】【CXF】【多介面】【身份認證】【Java】
阿新 • • 發佈:2018-11-02
需求:實現基於cxf框架的webservice
主要內容
- 實現基於cxf框架的webservice
- webservice釋出多個服務
- 實現webservice的安全訪問
開發環境
- jdk 1.8
- eclipse 2018-10-09版
- maven 3.5
- tomcat 8.5
除錯工具
- 瀏覽器
- SoapUI介面除錯工具(推薦**)
spring專案實現基於cxf框架的webservice
1、cxf jar包
<!-- cxf相關依賴 --> <!-- cxf-core --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.2.5</version> </dependency> <!-- cxf-rt-frontend-jaxws --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>3.2.5</version> </dependency> <!-- cxf-rt-transports-http --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>3.2.5</version> </dependency> <!-- cxf-rt-ws-security --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>3.2.5</version> </dependency>
2、spring.xml相關配置
約束名稱空間不能少
<!-- 下面是使用 cxf 配置的 webService --> <!-- define web service provider --> <!-- 配置第一個服務 --> <!-- 要暴露給外部呼叫的介面,address:請求路徑 --> <jaxws:endpoint id="jdWebService" implementor="com.zy.api.publish.route.jdWebServiceImpl" address="/jdWebService"> <!-- 新增客服端請求攔截器 --> <jaxws:inInterceptors> <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean> </jaxws:inInterceptors> <!-- 新增伺服器端響應攔截器 --> <jaxws:outInterceptors> <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean> </jaxws:outInterceptors> </jaxws:endpoint>
3、web.xml配置
<!-- webservice之cxf相關配置 --> <servlet> <servlet-name>CXF</servlet-name> <servlet-class>org.apache.cxf.transport.servlet.CXFServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>CXF</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
4、介面類編寫:
package com.zy.api.publish.route;
import javax.jws.WebParam;
import javax.jws.WebService;
/**
* @author TSY
*
* @Date: 2018-09-10
*
*
*/
@WebService(targetNamespace = "com.serviceTargetName")
public interface jdWebService {
public String waterPredictMinute(@WebParam(name = "jsonObj") String json);
public String waterPredictHour(@WebParam(name = "jsonObj") String json);
public String warning(@WebParam(name = "jsonObj") String json);
public String dispatcher(@WebParam(name = "jsonObj") String json);
public String closeLoopDispatcher(@WebParam(name = "jsonObj") String json);
public String closeLoopWater(@WebParam(name = "jsonObj") String json);
}
/////////////////// webservice開發說明 //////////////////////
/*
* @ 1.這裡的@Webservice(targetNamespace="")的作用類似與spring的Controller層中的Controller("/helloworld"),用於定位
* @ 2.在要釋出的服務介面類開頭加上@WebService 在介面的實現類開頭也加上@WebService 若兩個類不在同一個包中
* 則還要在實現類上用targetNamespace指明目標名稱空間。名稱空間的值和介面上的值一樣。
*
*/
5、實現類編寫
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.jws.WebService;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.core.enums.TypeEnum;
import com.zy.core.utils.DateUtil;
import com.zy.core.utils.FastJsonUtil;
import com.zy.core.utils.JacksonUtil;
import com.zy.system.entity.DispatcherScheme;
import com.zy.system.entity.PumpInfo;
import com.zy.system.entity.TempReviceData;
import com.zy.system.entity.WorkFlow;
import com.zy.system.service.DispatcherSchemeService;
import com.zy.system.service.PumpInfoService;
import com.zy.system.service.TempReciveDataService;
import com.zy.system.service.WorkFlowService;
import com.zy.system.websocket.CloseLoopDispatcherWebSocket;
import com.zy.system.websocket.JinhaiWebSocket;
import com.zy.system.websocket.WHGWebSocket;
import com.zy.system.websocket.WaterDemandWebSocket;
import com.zy.system.websocket.WorkFlowWebSocket;
/**
* @author TSY
*
* @Date: 2018-09-11
*
*/
/*
* 由於實現類和介面不在同一個包中。所以要加上targetNamespace屬性。
* 另外,這裡的endpointInterface是實現類對應介面的全路徑
*
*/
@WebService(targetNamespace="com.serviceTargetName",endpointInterface="com.zy.api.publish.route.jdWebService")
@Component("jdWebService")//spring注入用
public class jdWebServiceImpl implements jdWebService{
@Autowired
private XxxService xxxService;
//ip地址+埠+工程名+webxml配置路徑+spring配置的路徑+?wsdl
//url=localhost:8080/xxxx/services/jdWebService?wsdl
@Override
public String closeLoopDispatcher(String json) {
//業務處理
return "success";
}
@Override
public String closeLoopWater(String json) {
//業務處理
return "success";
}
@Override
public String waterPredictMinute(String json) {
return "success";
}
@Override
public String waterPredictHour(String json) {
return "success";
}
@Override
public String warning(String json) {
return "success";
}
@Override
public String dispatcher(String json) {
return "success";
}
}
/* 以下是webService 的註解說明
* @param endpointInterface指定接入點介面:介面必須存在
* @param targetNamespace service 名稱空間,一般為域名倒寫
* @param serviceName: 對外發布的服務名,指定 Web Service 的服務名
* 稱:wsdl:service。預設值為 Java 類的簡單名稱 + Service。(字串)
*/
6、驗證介面是否釋出成功
請求地址:
//ip地址+埠+工程名+webxml配置路徑+spring配置的路徑+?wsdl
//url=localhost:8080/xxx/services/jdWebService?wsdl
在瀏覽器中輸入訪問地址,出現wsdl文件說明,釋出成功啦
7、客戶端呼叫
- 最原始的http請求
- 通過生成客戶端程式碼工具自動生成請求程式碼生成Webservice客戶端的4種方法
-
純java 呼叫,不使用任何框架https://blog.csdn.net/chrisjingu/article/details/52385632
-
通過框架呼叫(略)
http請求的呼叫方法如下:
- Dom4j解析返回的xml檔案
- 需要拼裝請求的soapxml
package com.zy.api.recieve.action;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @author TSY
*
* @Date: 2018-09-25
*
*/
public class GiveWaterDemandService {
public static final String address = "http://xxxxx.nat123.cc:18837/?wsdl";//外網測試用
//public static final String address = "http://192.168.137.25:8000/?wsdl";//內網正式用
public static String giveWaterDemandToJD(String waterdemand) {
String result=null;
InputStream is =null;
try {
//第一步:建立服務地址
URL url = new URL(address);
//第二步:開啟一個通向服務地址的連線
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//第三步:設定引數
//3.1傳送方式設定:POST必須大寫
connection.setRequestMethod("POST");
//3.2設定資料格式:content-type
connection.setRequestProperty("content-type", "text/xml;charset=utf-8");
//3.3設定輸入輸出,因為預設新建立的connection沒有讀寫許可權,
connection.setDoInput(true);
connection.setDoOutput(true);
//第四步:組織SOAP資料,傳送請求
String soapXML = getXML(waterdemand);
//將資訊以流的方式傳送出去
OutputStream os = connection.getOutputStream();
os.write(soapXML.getBytes());
//第五步:接收服務端響應,列印
int responseCode = connection.getResponseCode();
if(200 == responseCode){//表示服務端響應成功
//獲取當前連線請求返回的資料流
is = connection.getInputStream();
/*
* dom4j解析返回的流資訊
*/
result = ParseSoapXML.result(is);
System.out.println("推送確認的需水量資料給交大結果++++++++++++++++++++++++"+result);
}
os.close();
}catch( IOException e) {
System.out.println("異常--推送確認的需水量資料給交大結果++++++++++++++++++++++++"+ e.getMessage());
}finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static String getXML(String waterdemand){
String soapXML ="<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tns=\"tns\">" +
"<soapenv:Header/>" +
"<soapenv:Body>" +
"<tns:waterdemand>" +
"<tns:data>"+
waterdemand
+ "</tns:data>" +
"</tns:waterdemand>" +
"</soapenv:Body>" +
"</soapenv:Envelope>"
;
return soapXML;
}
}
package com.zy.api.recieve.action;
import java.io.InputStream;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
*
* @author Administrator
*
* dom4j解析需水量webservice返回的流資訊
*/
public class ParseSoapXML {
public static String result(InputStream xml){
try {
SAXReader reader = new SAXReader();
Document doucment = reader.read(xml);
Element root = doucment.getRootElement();
Element bo = root.element("Body");
Element response = bo.element("waterdemandResponse");
Element re = response.element("waterdemandResult");
System.out.println(re.getText());
return re.getText();
} catch (DocumentException e) {
e.printStackTrace();
}
return "false";
}
}
webservice釋出多個服務
很簡單,將整個cxf相關的xml檔案塊,複製一份即可
然後,編寫相關的介面和實現類
<!-- 下面是使用 cxf 配置的 webService -->
<!-- define web service provider -->
<!-- 配置第一個服務 -->
<!-- 要暴露給外部呼叫的介面,address:請求路徑 -->
<jaxws:endpoint id="jdWebService"
implementor="com.zy.api.publish.route.jdWebServiceImpl"
address="/jdWebService">
<!-- 新增客服端請求攔截器 -->
<jaxws:inInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<!-- 新增伺服器端響應攔截器 -->
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
<!-- 配置第二個服務 -->
<jaxws:endpoint id="tempWebService"
implementor="com.zy.api.publish.route.egbWebServiceImpl"
address="/egb/thirdParty/toData">
<jaxws:inInterceptors> <!-- WS-Security攔截器 -->
<bean
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<!-- 密碼型別,PasswordText表示明文,密文是PasswordDigest -->
<entry key="passwordType" value="PasswordText" />
<entry key="passwordCallbackRef"> <!-- 回撥函式引用 -->
<ref bean="myPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
實現webservice的安全訪問
1、匯入security相關的jar包
2、配置xml檔案
WS-Security攔截器,回掉函式需要注入到bean(<bean class="com.zy.api.publish.route.ServerPasswordCallback"
id="myPasswordCallback" />)、回撥函式主要用來對密碼和賬戶進行確認的,一般可以從資料庫進行查詢,
<jaxws:endpoint id="tempWebService"
implementor="com.zy.api.publish.route.egbWebServiceImpl"
address="/egb/thirdParty/toData">
<jaxws:inInterceptors> <!-- WS-Security攔截器 -->
<bean
class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<!-- 密碼型別,PasswordText表示明文,密文是PasswordDigest -->
<entry key="passwordType" value="PasswordText" />
<entry key="passwordCallbackRef"> <!-- 回撥函式引用 -->
<ref bean="myPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
</jaxws:inInterceptors>
<jaxws:outInterceptors>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
</jaxws:outInterceptors>
</jaxws:endpoint>
<bean
class="com.zy.api.publish.route.ServerPasswordCallback"
id="myPasswordCallback" />
2、編寫回調函式
package com.zy.api.publish.route;
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.wss4j.common.ext.WSPasswordCallback;
/*
* 參考部落格:https://blog.csdn.net/chrisjingu/article/details/52385632
*/
public class ServerPasswordCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if ("ThirdParty".equals(pc.getIdentifier())) {
pc.setPassword("egbIh87wmnHbFiKqad9M8NNtdql");
}
}
}
/*
* 使用UsernameToken方式驗證,在server端的回撥函式中WSPasswordCallback只能獲得使用者名稱(identifier),一般這裡的邏輯是使用這個使用者名稱到資料庫中查詢其密碼,
* 然後再設定到password屬性,WSS4J 會自動比較客戶端傳來的值和你設定的這個值。你可能會問為什麼這裡CXF 不把客戶端提交的密碼傳入讓我們在ServerPasswordCallbackHandler中比較呢?
* 這是因為客戶端提交過來的密碼在SOAP 訊息中已經被加密為MD5 的字串,如果我們要在回撥方法中作比較,那麼第一步要做的就是把服務端準備好的密碼加密為MD5 字串,
* 由於MD5 演算法引數不同結果也會有差別,另外,這樣的工作CXF 替我們完成了.
*/
/*
* 使用ws-security進行安全驗證,概括來說分兩步:
一、建立一個驗證類,如本例項的ServerPasswordCallback.class
二、配置WSS4JInInterceptor攔截器,如applicationContext-cxf.xml所示。
三、將applicationContext-cxf.xml配置到web.xml的contextConfigLocation屬性中
*該類實現ws-security的CallbackHandler 介面,並重寫它的handle方法。該類就是身份驗證的主要類,
*當客戶端傳過的使用者名稱中為“huwei“時,該方法會將指定的密碼告知ws-security的WSPasswordCallback 類,
*並讓它後期去和客戶端的密碼進行驗證,通過就放行,否則打回。
*在applicationContext-cxf.xml中該類會作為WSS4JInInterceptor攔截器的回撥函式屬性,進行配置。
*/
3、客戶端測試(SoapUI)
【瀏覽器、soapui都可以進行檢視,但是無法進行正確呼叫,需要設定使用者名稱和密碼】
提示如下錯誤:
需要設定使用者名稱和密碼;
比如:soapui直接再header裡面加上如下資訊,即可
用其他框架也可以設定使用者名稱和密碼進行請求
小技巧:http請求時,xml檔案可以直接copy 工具SoapUI生成的xml檔案
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>ThirdParty</wsse:Username>
<wsse:Password Type= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
egbIh87wmnHbFiKqad9M8NNtdql</wsse:Password>
</wsse:UsernameToken> </wsse:Security>
<soapenv:Header/>