由於整合過程困難重重,特此記錄,希望能幫到遇到同樣問題的大家。
假設有一個需求:前臺程序需要調用後臺程序的“檢測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
至此,整合完成,需求也實現了。