在OSGI环境中,通常使用CXF-DOSGI作为webservice发布框架,这种资料网上很多,但是如何在CXF-DOSGI下做webservice的权限校验,网上资料几乎为零。
关于CXF(注意不是CXF-DOSGI)做权限校验的资料一堆堆的。本人经过试验,解决了这个问题,说好的代码如下:
一、服务端的
public class Activator implements BundleActivator {
@SuppressWarnings("rawtypes")
private ServiceRegistration registration;
private static BundleContext context;
static BundleContext getContext() {
return context;
}
@Override
public void start(BundleContext bundleContext) throws Exception {
Activator.context = bundleContext;
//设置服务的属性
Dictionary<String, String> props = new Hashtable<String, String>();
props.put("service.exported.interfaces","*");
props.put("service.exported.intents","SOAP");
// 发布服务的类型,有ws类型的,这种是基于SOAP协议的,还有一种是REST类型的
props.put("service.exported.configs","org.apache.cxf.ws");
props.put("org.apache.cxf.ws.address","http://localhost:9000/helloWorld"); // webservice的发布地址
// 这里是增加权限校验拦截器的关键,这里是个数组可以有多个拦截器
String[] interceptors = new String[] {"test.AuthInterceptor"};
props.put("org.apache.cxf.ws.in.interceptors", interceptors);
//注册服务,这是OSGI同普通项目不同的地方,是以这种方式发布的
registration = Activator.context.registerService(HelloWorldService.class.getName(), new HelloWorldServiceImpl(), props);
}
@Override
public void stop(BundleContext bundleContext) throws Exception {
Activator.context = null;
registration.unregister();
}
}
// webservice接口代码
public interface HelloWorldService {
String sayHello();
}
// webservice实现类
public class HelloWorldServiceImpl implements HelloWorldService {
@Override
public String sayHello() {
System.out.println("HelloWorld");
return (new Date()).toString();
}
}
// 权限校验拦截器
package test.service;
import gboat2.base.core.util.SpringContextUtil;
import gboat2.cxf.business.IWebServiceConfigBusiness;
import gboat2.cxf.model.WebServiceConfig;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import org.apache.commons.lang3.StringUtils;
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.message.Exchange;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.service.Service;
import org.apache.cxf.service.invoker.BeanInvoker;
import org.w3c.dom.NodeList;
/**
* webservice权限拦截器
* <p>
* 根据用户名和密码进行拦截
* </p>
* @author zhaic
* @since jdk1.6
* 2016年6月22日
*
*/
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private SAAJInInterceptor saa = new SAAJInInterceptor();
public AuthInterceptor() {
super(Phase.PRE_PROTOCOL); // 注意这个配置是拦截器被触发的阶段不能错
getAfter().add(SAAJInInterceptor.class.getName());
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
// 获取到当前服务的接口名称,这里如果不需要也可以不要,我这里纯属是为了解决我们自己的业务问题
Exchange exchange = message.getExchange();
Service service = exchange.get(Service.class);
Class<?>[] interfaces = ((BeanInvoker)service.getInvoker()).getServiceObject(exchange).getClass().getInterfaces();
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 (Exception e) {
e.printStackTrace();
}
if (head == null) {
return;
}
NodeList loginNameNodes = head.getElementsByTagName("loginName");
NodeList passwordNodes = head.getElementsByTagName("password");
if((null == loginNameNodes || loginNameNodes.getLength() == 0)
|| (null == passwordNodes || passwordNodes.getLength() == 0)) { // 以下边这种方式抛出异常可以被客户端接到
SOAPException soapExc = new SOAPException("loginName and password can not null!");
throw new Fault(soapExc);
}
String loginName = loginNameNodes.item(0).getTextContent();
String password = passwordNodes.item(0).getTextContent();
boolean validateResult = validateAuth(interfaces, loginName, password);
if (validateResult) {
SOAPException soapExc = new SOAPException("authentication failed!");
throw new Fault(soapExc);
} else {
SOAPException soapExc = new SOAPException("webservice authentication error!");
throw new Fault(soapExc);
}
}
/**
* 校验用户权限
* @param clazzs 用户发布的服务类所实现的接口列表
* @param loginName
* @param password
* @return
*/
private boolean validateAuth(Class<?>[] clazzs, String loginName, String password) {
// 这里写相关的权限校验代码
}
}
二、客户端的代码基本如下:
调用部分代码,这里用的是非动态客户端,至于动态客户端可以参考其他例子,也不过是增加个拦截器而已,这里不再赘述;
public class TestWebServiceResult {
public staitc void main(String[] args) {
AuthorityParameter param = new AuthorityParameter("loginName", "anshun", "password", "123456");
JaxWsProxyFactoryBean factoryBean=new JaxWsProxyFactoryBean();
factoryBean.getInInterceptors().add(new AuthorityHeaderInterceptor(param));
factoryBean.setServiceClass(HelloWorldService.class);
factoryBean.setAddress("http://localhost:9000/helloWorld");
HelloWorldService service = (IHelloWorldService)factoryBean.create();
System.out.println(service.sayHello());
}
}
// 权限代码拦截器
package test.utils;
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;
/**
* <p>
* 调用webservice时需要传入用户名和密码
* </p>
* @author zhaic
* @since jdk1.6
* 2015年11月17日
*
*/
public class AuthorityHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage>{
private AuthorityParameter authorityParameter;
public AuthorityHeaderInterceptor(AuthorityParameter authorityParameter) {
super(Phase.PREPARE_SEND);
this.authorityParameter = authorityParameter;
}
public void handleMessage(SoapMessage msg) throws Fault {
List<Header> headers = msg.getHeaders();
//创建Document对象
Document doc = DOMUtils.createDocument();
//配置服务器端Head信息的用户密码
Element eleId= doc.createElement(this.authorityParameter.getUserNameKey());
eleId.setTextContent(this.authorityParameter.getUserNameValue());
Element elePass = doc.createElement(this.authorityParameter.getPasswordKey());
elePass.setTextContent(this.authorityParameter.getPasswordValue());
/**
* 也可以先创建一个父节点,则生成的XML文档 ,我们这里是直接使用用户名和密码
* <authHeader>
* <userId>lzw</userId>
* <userPass>123456</userPass>
* </authHeader>
*/
headers.add(new Header(new QName(""), eleId));
headers.add(new Header(new QName(""), elePass));
}
}
密码参数类:
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;
}
}
好了,以上就是全部的内容,难点在于在CXF-DOSGI如何加入拦截器,这里的资料实在是少之又少。即使是Apache官方也没有直接的例子,不过CXF和CXF-DOSGI毕竟是师出同门,有很多相似之处,本人就是通过参考CXF的例子结合仅有的一页CXF-DOSGI的参数配置说明解决了这个问题。这个问题突破后,还可以写其他功能的拦截器,比如可以取出客户端的IP(注意,由于CXF默认使用的是HTTP协议,所以是可以使用HttpServletRequest等类的)来进行白名单过滤。希望啰嗦了半天能帮上网友。
附重要帮助文档:
CXF-DOSGI配置档,里边有很多配置在其他地方根本找不到,因此尤其重要:
https://cwiki.apache.org/confluence/display/CXF/Distributed+OSGi+Reference
CXF拦截器文档:
http://cxf.apache.org/docs/interceptors.html