CXF3.0.3和Spring3.24進行整合

由於整合過程困難重重,特此記錄,希望能幫到遇到同樣問題的大家。

假設有一個需求:前臺程序需要調用後臺程序的“檢測IP地址重複”服務,需要進行用戶認證。

服務器端(後臺)

一、定義提供服務的接口以及實現類

接口:

import java.util.List;

import javax.jws.WebService;

@WebService
public interface DataCenter {
	List<String> getIptvprofileMacListByIpAddress(String ip);
}

實現類:

import java.util.List;

import javax.annotation.PostConstruct;
import javax.jws.WebService;

import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.staxutils.StaxUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import com.google.common.collect.Lists;
import com.smt.iptv.portal.entity.Iptvprofile;
import com.smt.iptv.portal.service.cmp.IptvprofileService;

@Controller
@WebService
public class DataCenterImpl implements DataCenter{
	@Autowired
	private IptvprofileService iptvprofileService;
	
	// CXF 報java.lang.RuntimeException: Cannot create a secure XMLInputFactory的解決方案
	@PostConstruct
	public void init() {
		System.setProperty(StaxUtils.ALLOW_INSECURE_PARSER, "true");
	}
	
	/**
	 * 
	 * @title: getIptvprofile
	 * @description: 根據ipaddress查詢Iptvprofile,如果查詢到數據,返回Iptvprofile的macaddress組成的集合
	 * @author: Jack
	 * @version: V1.00
	 * @date: 2019年2月19日 下午10:40:39
	 * @param ip
	 * @return
	 */
	@Override
	public List<String> getIptvprofileMacListByIpAddress(String ip) {
		List<Iptvprofile> profileList = this.iptvprofileService.getByIpaddress(ip);
		List<String> macList = Lists.newArrayList();
		if (profileList != null && profileList.size() > 0) {
			for(Iptvprofile profile : profileList) {
				String mac = profile.getMacaddress();
				if (StringUtils.isNotBlank(mac)) {
					macList.add(mac);
				}
			}
		}
		
		return macList;
	}
}

這裏說一下,後面在調用webService 時,遇到了一個安全性異常,解決思路來源於:https://blog.csdn.net/Swear_fling/article/details/45232875

二、定義CXF輸入攔截器,進行用戶認證

攔截器代碼:

import javax.xml.namespace.QName;

import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoader;
import org.springside.modules.security.utils.Digests;
import org.springside.modules.utils.Encodes;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.smt.iptv.portal.entity.role.User;
import com.smt.iptv.portal.service.account.UserService;

/**
 * 
 * Title: LoginVerifyInterceptor
 * Description: webService攔截器,進行登錄驗證,驗證不通過則不進行接口方法調用。另外,shiro要開放相應url權限,否則會導向到登錄界面的jsp。
 * @author Jack
 * @date   2019年2月21日 下午7:07:04
 *
 */
public class LoginVerifyInterceptor extends AbstractPhaseInterceptor<SoapMessage>{

	public LoginVerifyInterceptor() {
		super(Phase.PRE_INVOKE);		// 在調用接口方法之前進行攔截
	}

	@Override
	public void handleMessage(SoapMessage message) throws Fault {
		// TODO 從客戶端發送的message中獲取header,header中存放着要進行驗證的用戶名、密碼
		Header header = message.getHeader(new QName("authcUser"));
		if (header == null) {
			throw new Fault(new IllegalArgumentException("沒有進行登錄驗證,不允許調用"));
		}
		
		Element element = (Element) header.getObject();
		NodeList usernameNodeList = element.getElementsByTagName("username");
		NodeList passwordNodeList = element.getElementsByTagName("password");
		if (usernameNodeList.getLength() != 1 || passwordNodeList.getLength() != 1) {
			throw new Fault(new IllegalArgumentException("用戶名或密碼格式不對"));
		}
		
		String username = usernameNodeList.item(0).getTextContent();
		String password = passwordNodeList.item(0).getTextContent();
		if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
			throw new Fault(new IllegalArgumentException("用戶名或密碼不能爲空"));
		}
		
		// TODO 根據用戶名、密碼,查詢數據庫,是否有對應的後臺用戶
		ApplicationContext ac = ContextLoader.getCurrentWebApplicationContext();
		UserService userService = (UserService) ac.getBean("userService");
		
		User user = userService.findUserByLoginName(username);
		if (user == null) {
			// 根據用戶名查不到
			throw new Fault(new IllegalArgumentException("用戶不存在"));
		}else {
			// 將客戶端發送的密碼經過相同的加密算法進行加密後,和數據庫的密碼進行比較
			byte[] input = Digests.sha1(password.getBytes(), Encodes.decodeHex(user.getSalt()), 1024);
			String encryptPassword = Encodes.encodeHex(input);
			if (!encryptPassword.equals(user.getPassword())) {
				throw new Fault(new IllegalArgumentException("密碼不正確"));
			}
		}
	}

}

三、web.xml文件中註冊CXF所需servlet

<servlet>
		<description>配置CXF所需servlet</description>
		<servlet-name>CXFServlet</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
		<load-on-startup>10</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/webservice/*</url-pattern>
	</servlet-mapping>

四、spring配置文件中,配置一個JAX-WS server

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:jaxws="http://cxf.apache.org/jaxws"
	xsi:schemaLocation="http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"
	default-lazy-init="true">
	
	<description>Spring公共配置</description>
	
	<!-- 引入cxf的xml文件,該文件在cxf core.jar包下 -->
	<import resource="classpath:META-INF/cxf/cxf.xml"/>
	<bean id="LoginVerifyInterceptor" class="com.smt.iptv.portal.web.cxf.LoginVerifyInterceptor"/>
	<!-- 發佈webService -->
	<jaxws:endpoint id="DataCenterImpl" implementor="com.smt.iptv.portal.web.cxf.DataCenterImpl" 
		address="datacenter">
		<!-- 設置登錄驗證攔截器 -->
		<jaxws:inInterceptors>
			<ref bean="LoginVerifyInterceptor"/>
		</jaxws:inInterceptors>	
	</jaxws:endpoint>
</beans>

到這裏,服務器端的代碼及配置就結束了。

客戶端(前臺)

一、首先利用CXF提供的工具,生成客戶端代碼

下載並解壓cxf後,進入bin目錄(當然,可以通過配置環境變量完成)運行命令:wsdl2java -p com.jack.webservice.datacenter -d d:\src -client  http://www.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl

-p後面是你要生成的包結構,-d後面是你生成文件的保存路勁,-client後面是你要使用的服務端訪問地址.

 

生成代碼之後將代碼拷貝到你的項目中,也可以打成jar包後導入

二、定義CXF輸出攔截器,發送用戶名、密碼

攔截器代碼:

import java.util.List;

import javax.xml.namespace.QName;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * 
 * Title: LoginVerifyInterceptor
 * Description: webService攔截器,遠程調用別人的接口方法,人家要求進行登錄驗證。
 * @author Jack
 * @date   2019年2月21日 下午7:40:18
 *
 */
public class LoginVerifyInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

	private String username;
	private String password;
	
	public LoginVerifyInterceptor(String username, String password) {
		super(Phase.PREPARE_SEND);		// 準備發送調用請求前攔截	
		this.username = username;
		this.password = password;
	}

	@Override
	public void handleMessage(SoapMessage message) throws Fault {
		
		// TODO 將用戶名、密碼生成xml,往message的Header集合中添加
		Document doc = DOMUtils.createDocument();
		// 生成用戶名元素節點
		Element usernameElement = doc.createElement("username");
		usernameElement.setTextContent(this.username);
		// 生成密碼元素節點
		Element passwordElement = doc.createElement("password");
		passwordElement.setTextContent(this.password);
		
		Element element = doc.createElement("authcUser");
		element.appendChild(usernameElement);
		element.appendChild(passwordElement);
		
		List<Header> headerList = message.getHeaders();
		headerList.add(new Header(new QName("authcUser"), element));
	}

}

三、客戶端調用webService發佈的接口方法

		/*DataCenterImplService dataCenterImpl = new DataCenterImplService();
		DataCenter dc = dataCenterImpl.getDataCenterImplPort();
		// 在調用遠程方法前,設置輸出攔截器,進行登錄驗證(別人說驗證不過不給調用)
		Client client = ClientProxy.getClient(dc);
		client.getOutInterceptors().add(new LoginVerifyInterceptor("admin", "123"));*/
		
		
		/**
		 * 用上面的方法,調遠程方法會報異常:com.sun.xml.ws.client.sei.SEIStub cannot be cast to org.apache.cxf.frontend.ClientProxy
		 * 原因可能是因爲CXF的JAX-WS實現沒有裝載,而是裝載的SUN對JAX-WS的實現。通過配置文件修改classloader裝載時不委派依舊無效!!!
		 */
		JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
		factory.getOutInterceptors().add(new LoginVerifyInterceptor("admin", "123456"));
		factory.setServiceClass(DataCenter.class);
		factory.setAddress("http://localhost:9999/iptvmanager/webservice/datacenter?wsdl");
		DataCenter dc = (DataCenter) factory.create();
		// 調用遠程方法
		List<String> result = dc.getIptvprofileMacListByIpAddress(request.getRemoteHost());

這裏又遇到一個異常:com.sun.xml.ws.client.sei.SEIStub cannot be cast to org.apache.cxf.frontend.ClientProxy。

解決思路來源於:https://stackoverflow.com/questions/2064068/how-to-pick-cxf-over-metro-on-glassfish

至此,整合完成,需求也實現了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章