1. 程式人生 > >【WebService】【CXF】【多介面】【身份認證】【Java】

【WebService】【CXF】【多介面】【身份認證】【Java】

需求:實現基於cxf框架的webservice

主要內容

  1. 實現基於cxf框架的webservice
  2. webservice釋出多個服務
  3. 實現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、客戶端呼叫

  1. 最原始的http請求
  2. 通過生成客戶端程式碼工具自動生成請求程式碼生成Webservice客戶端的4種方法
  3. 純java 呼叫,不使用任何框架https://blog.csdn.net/chrisjingu/article/details/52385632

  4. 通過框架呼叫(略)

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/>