由于整合过程困难重重,特此记录,希望能帮到遇到同样问题的大家。
假设有一个需求:前台程序需要调用后台程序的“检测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
至此,整合完成,需求也实现了。