项目中有个需求,需要使用CXF动态客户端调用webservice服务端,这个服务端是JDK自带的webservice发布的,而且我们需要在调用时传入用户名和密码。网上CXF客户端和服务端配套使用webservice的方法很多,这里不再赘述,这里主要讲下我上边说的特殊使用方式。有人会把用户名和密码放在webservice的请求参数里,这样污染了webservice接口,本人不喜欢这种方式。
开始的时候,我先试了CXF拦截器传送密码的方式,我自己是参照类似的文章写的:点击打开链接,结果发现出现很多奇怪的错误,比如各种命名空间不识别,JAXB解组错误等等,压根就没有进入服务端的handler,当然了服务端的总入口也是拿到了请求,只是在进入handler之前就报错了。按说CXF也是遵守了webservice的国际规范了应该可以的。后来看了CXF中的传入用户名和密码所涉及的类和参数,分析出CXF应该是对SOAP的head操作做了封装,所以导致SOAP中的具体数据内容和JDK自带的webservice是不同的,怎么办呢?后来想到我可以直接自定义SOAP头,然后到服务端的handler中拿到这个头进行分析不就解决了?经过各种调试,终于解决了。下边公布代码:
1、服务端的handlers.xml
2、服务端的handler代码:
/**
*
*/
package com.cms.webservice;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.Node;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import javax.xml.ws.soap.SOAPFaultException;
import com.jeecms.cms.Constants;
import com.jeecms.common.util.PropertyUtils;
/**
* 服务端权限校验类
*
* @author zhaichong
*
* 2014年10月1日
*/
public class AuthorityHandler implements SOAPHandler
private static String PROPERTIE_NAME="webservice.properties";
private static String USER_NAME_PWD="webservice.userNamePwd";
public boolean handleMessage(SOAPMessageContext context) {
return auth(context);
}
/**
* 校验用户名密码是否合法
*
* @param context
* @return
*/
private boolean auth(SOAPMessageContext context) {
Boolean out = (Boolean) context.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!out) {
SOAPMessage message = context.getMessage();
try {
SOAPHeader header = message.getSOAPPart().getEnvelope().getHeader();
if (header != null) {
// 获取SOAP头登录校验节点
// org.w3c.dom.Node node =
// header.getElementsByTagName("authHeader").item(0);
// 获取用户名
org.w3c.dom.Node userNameNode = header.getElementsByTagName("userName").item(0);
String userName = userNameNode.getTextContent();
// 获取密码
org.w3c.dom.Node passwordNode = header.getElementsByTagName("password").item(0);
String password = passwordNode.getTextContent();
// 获取本地配置文件用户名密码 与传过来的用户名密码比对
PropertiesUtil propertiesUtil = new PropertiesUtil(PROPERTIE_NAME);
String userNamePWD = propertiesUtil.getKeyValue(USER_NAME_PWD);
if (userNamePWD != null) {
String namePWDs[] = userNamePWD.split(",");
for (int i = 0; i < namePWDs.length; i++) {
if (namePWDs[i] != null) {
String uName = namePWDs[i].split("\\|")[0];
String uPassword = namePWDs[i].split("\\|")[1];
if (uPassword.equals(password) && uName.equals(userName)) {
return true;
}
}
}
}
//System.out.println("client send userName:" + userName);
//System.out.println("client send password:" + password);
return false;
} else {
return false;
}
} catch (SOAPException e) {
e.printStackTrace();
}
}
return true;
}
public boolean handleFault(SOAPMessageContext context) {
// TODO Auto-generated method stub
return false;
}
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
public Set
// TODO Auto-generated method stub
return null;
}
}
注意: 这里的handler.xml文件在war包里是放在classes下的,webservice的实现类要用@HandlerChain(file="handlers.xml")进行注解,上边代码的用户名和密码是放在properties文件中的,各位网友可以自己修改。
3、客户端的拦截器:
package gboat2.cxf.test;
import gboat2.cxf.utils.AuthorityParameter;
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;
/**
*
* 调用webservice时需要传入用户名和密码
*
4、用户名和密码类:
package gboat2.cxf.utils;
public class AuthorityParameter {
/**
* 用户名字段的名称
*/
private String userNameKey;
/**
* 用户名字段的值
*/
private String userNameValue;
/**
* 密码字段的名称
*/
private String passwordKey;
/**
* 密码字段的值
*/
private String passwordValue;
public AuthorityParameter() {
super();
}
/**
* AuthorityParameter
* @param userNameKey 用户名的字段名称
* @param userNameValue 用户名的字段值
* @param passwordKey 密码的字段名称
* @param passwordValue 密码的字段值
*/
public AuthorityParameter(String userNameKey, String userNameValue, String passwordKey, String passwordValue) {
super();
this.userNameKey = userNameKey;
this.userNameValue = userNameValue;
this.passwordKey = passwordKey;
this.passwordValue = passwordValue;
}
public String getUserNameKey() {
return userNameKey;
}
public void setUserNameKey(String userNameKey) {
this.userNameKey = userNameKey;
}
public String getUserNameValue() {
return userNameValue;
}
public void setUserNameValue(String userNameValue) {
this.userNameValue = userNameValue;
}
public String getPasswordKey() {
return passwordKey;
}
public void setPasswordKey(String passwordKey) {
this.passwordKey = passwordKey;
}
public String getPasswordValue() {
return passwordValue;
}
public void setPasswordValue(String passwordValue) {
this.passwordValue = passwordValue;
}
}
5、调用代码
JaxWsDynamicClientFactory factory = JaxWsDynamicClientFactory.newInstance();
Client client = null;
// 创建客户端连接
client = factory.createClient("http://localhost:9090/cms/saveCms?wsdl");
AuthorityParameter param = new AuthorityParameter("userName", "anshun", "password", "123456");
client.getOutInterceptors().add(new AuthorityHeaderInterceptor(authorityParameter));
client.getOutInterceptors().add(new LoggingOutInterceptor());
// 客户端设置
HTTPClientPolicy policy = ((HTTPConduit) client.getConduit()).getClient();
policy.setConnectionTimeout(30000);
policy.setReceiveTimeout(180000);
// 以下就是具体调用方法,这里不写了,网上很多。
......
好了,这样在服务端就能获取到用户名和密码,又不会污染webservice的方法参数。记下来防止遗忘,也为各位网友解决这种问题提供个思路,网上这种资料太少了。其中的部分代码是从项目代码里摘录出来的,可能有的地方有纰漏,但是思路脉络已经清除了,希望能帮到各位网友。另外,如果有需要了解JDK自带webservice如何使用用户名和密码来进行调用前的校验,可以参考我的一个例子:点击打开链接