有时候我们的webservice在服务端需要做一个调用方的验证,以保证我们的服务只有指定的客户才能使用。虽然可以使用wss4j的方法来做安全验证,但是考虑到我们的项目会与被.net平台下的项目调用,为了避免跨平台间出现的问题,我们还是决定采用自定义soapheader的形式来添加验证信息。
先来看一下客户端发起请求的soap内容
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:apac="http://apache.org/">
<soapenv:Header/>
<soapenv:Body>
<apac:CyfTest>
<!--Optional:-->
<arg0>?</arg0>
<!--Optional:-->
<arg1>?</arg1>
</apac:CyfTest>
</soapenv:Body>
</soapenv:Envelope>
其中红字部分就是我们要添加自定义信息的位置,再来看一下客户端的spring配置文件
<bean id="TsbServiceFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="address"
value="http://localhost:8080/TsbWebService/Cyf?wsdl"></property>
<property name="serviceClass" value="tsb.ws.tsbinterface.ICyfClient"></property>
<property name="outInterceptors">
<list>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<bean class="tsb.ws.common.authentication.AddPptSoapHeader"></bean>
</list>
</property>
</bean>
其中红色部分指定的类就是用来在请求的soap协议中加上自定义头部信息的处理类,处理类内容如下
package tsb.ws.common.authentication;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapHeader;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* 添加SoapHeader拦截器
* @author cyf
*
*/
public class AddSoapHeader extends AbstractSoapInterceptor {
public AddSoapHeader() {
super(Phase.WRITE);
}
/**
* 处理soap信息
* @param message soap信息
* <P>作成者:cyf
*/
public void handleMessage(SoapMessage message) throws Fault {
// SoapHeader部分待添加的节点
QName qName = new QName("CertificationProxy");
Document doc = DOMUtils.createDocument();
// 验证用户名
Element id = doc.createElement("userid");
id.setTextContent("xxx");
// 验证密码
Element pwd = doc.createElement("userpwd");
pwd.setTextContent("xxx");
Element root = doc.createElementNS("http://tempuri.org/","CertificationProxy");
root.appendChild(id);
root.appendChild(pwd);
// 创建SoapHeader内容
SoapHeader header = new SoapHeader(qName, root);
// 添加SoapHeader内容
List<Header> headers = message.getHeaders();
headers.add(header);
}
}
在调用服务的时候客户端就可以在soap请求的头部添加如下的信息其中userid和userpwd就是我们用来验证的用户名和密码
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:apac="http://apache.org/">
<soapenv:Header>
<CertificationProxy xmlns="http://tempuri.org/">
<userid>xxx</userid>
<userpwd>xxx</userpwd>
</CertificationProxy>
</soapenv:Header>
<soapenv:Body>
<apac:CyfTest>
<!--Optional: -->
<arg0>?</arg0>
<!--Optional: -->
<arg1>?</arg1>
</apac:CyfTest>
</soapenv:Body>
</soapenv:Envelope>
服务端截取请求soap协议时需要在配置文件中添加拦截器
<jaxws:endpoint id="TsbWebService" implementor="tsb.ws.tsbimpl.TsbWebServiceImpl" address="/Tsb">
<jaxws:inInterceptors>
<!-- 日志拦截器 -->
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<!-- 自定义拦截器,用于实现认证操作 -->
<bean class="tsb.ws.common.authentication.ReadSoapHeader" />
</jaxws:inInterceptors>
<jaxws:serviceFactory>
<ref bean="jaxWsServiceFactoryBean" />
</jaxws:serviceFactory>
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
</jaxws:endpoint>
拦截器内容如下,主要就是从soap协议中获取head部分的节点,然后拿头的值做判断。
package tsb.ws.common.authentication;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.NodeList;
public class cnm extends AbstractPhaseInterceptor<SoapMessage> {
// 取得Log实例
private static Log log = LogFactory.getLog(ReadSoapHeader.class);
private SAAJInInterceptor saa = new SAAJInInterceptor();
public cnm() {
super(Phase.PRE_PROTOCOL);
getAfter().add(SAAJInInterceptor.class.getName());
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
// 获取soap信息
SOAPMessage mess = message.getContent(SOAPMessage.class);
if (mess == null) {
saa.handleMessage(message);
mess = message.getContent(SOAPMessage.class);
}
SOAPHeader head = null;
try {
head = mess.getSOAPHeader();
} catch (SOAPException e) {
e.printStackTrace();
}
if (head == null) {
return;
}
try {
// 读取soap头中的userid节点
NodeList nodes = head.getElementsByTagName("userid");
// 读取soap头中的userid节点
NodeList nodepass = head.getElementsByTagName("userpwd");
// 此处可加各种验证
} catch (Exception e) {
SOAPException soapExc = new SOAPException("验证失败");
throw new Fault(soapExc);
}
}
}
****************************************************************************************************************************************************
另外也记一下用wss4j的UsernameToken进行验证的配置,基本上和自定义soapheader也差不多同样是采用拦截器的方法
客户端配置文件的endpoint中添加如下拦截器,其中clientPasswordCallback用于指定添加验证信息的处理类;
UsernameToken是指使用用户名令牌;PasswordText指密码加密策略,这里直接文本;xxx 指别名。
<bean id="TsbServiceFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean">
<property name="address"
value="http://localhost:8080/TsbWebService/Cyf?wsdl"></property>
<property name="serviceClass" value="tsb.ws.tsbinterface.ICyfClient"></property>
<property name="outInterceptors">
<list>
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordText" />
<entry key="user" value="xxx" />
<entry key="passwordCallbackRef">
<ref bean="clientPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
</list>
</property>
</bean>
添加验证信息
package cxf.client;
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.ws.security.WSPasswordCallback;
public class ClientPasswordCallback implements CallbackHandler {
@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback ws = (WSPasswordCallback) callbacks[0];
ws.setPassword("xxxx");
ws.setIdentifier("xxxx");
}
}
服务端通过拦截器接收验证信息,serverPasswordCallback表示验证处理的类
<jaxws:endpoint id="TsbWebService" implementor="tsb.ws.tsbimpl.TsbWebServiceImpl"
address="/Tsb">
<jaxws:inInterceptors>
<!-- 日志拦截器 -->
<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
<!-- 自定义拦截器,用于实现认证操作 -->
<bean class="tsb.ws.common.authentication.ReadSoapHeader" />
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken" />
<entry key="passwordType" value="PasswordText" />
<entry key="user" value="xxx" />
<entry key="passwordCallbackRef">
<ref bean="serverPasswordCallback" />
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
<jaxws:serviceFactory>
<ref bean="jaxWsServiceFactoryBean" />
</jaxws:serviceFactory>
<jaxws:features>
<bean class="org.apache.cxf.feature.LoggingFeature" />
</jaxws:features>
</jaxws:endpoint>
服务端获取客户端发来的验证信息方法如下
WSPasswordCallback ws = (WSPasswordCallback) callbacks[0];
// 获取用户名
String identifier = ws.getIdentifier();
// 获取用户密码
String password = ws.getPassword();