cxf WebService整理

项目中需要用到CXF做WS处理,花点时间对其有个简单认识,主要是在安全认证以及日志记录和异常处理这块有要求控制。

安全认证采用的是WSS4J,日志记录和异常处理采用拦截器控制,

资源下载:客户端和服务端都点这

服务端

整体架构.

至于webservice的配置可以参考其他文档,

服务接口

package com.cxfdemo.ws.service;

import java.util.List;

import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

import com.cxfdemo.ws.service.model.Resume;
import com.cxfdemo.ws.service.model.User;

@WebService
public interface HelloWorld {

	@WebResult(name = "String")
	public String sayHi(@WebParam(name="text")String text);

	@WebResult(name = "user")
	public User getUser(@WebParam(name="id")String id);

	public List<User> getAllUsers();

	public void saveUser(@WebParam(name="id")String id,
			@WebParam(name="name")String name,
			@WebParam(name="sex")int sex);

	/**
	 * 客户端的ObjectFactory的createUser方法的参数必须和User的构造函数的参数一致
	 * <p>
	 * 如客户端中需要用到new User("id","name",1)构造User对象时
	 *
	 * 需要在ObjectFactory中加入
	 * 	public User createUser(String id,String name,int sex) {
     * 		return new User(id,name,sex);
     *	}
	 * </p>
	 * @param user
	 */
	public void saveUsers(@WebParam(name="user")User user);

	@WebResult(name = "String")
	public String saveResumes(@WebParam(name="resume")Resume resume); 

}

服务实现

package com.cxfdemo.ws.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.activation.DataHandler;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;

import com.cxfdemo.ws.service.model.Resume;
import com.cxfdemo.ws.service.model.User;

@WebService(endpointInterface="com.cxfdemo.ws.service.HelloWorld",
serviceName="HelloWorldServiceService",portName="HelloWorldServicePort",
name="HelloWorldService",targetNamespace="http://service.ws.cxfdemo.com/")
public class HelloWorldService implements HelloWorld{

	public String sayHi(String text) {
		return "Hello " + text;
	}

	public User getUser(String id) {
		return new User("大王", id, 1);
	}

	public List<User> getAllUsers() {
		List<User> list = new ArrayList<User>();
		for (int i=0;i<4;i++) {
			list.add(new User("小明"+i,""+i,i));
		}
		return list;
	}

	public void saveUser(String id, String name, int sex) {
		System.out.println(new User(id, name, sex));
	}

	public void saveUsers(User user) {
		System.out.println(user);
	}

	public String saveResumes(@XmlMimeType("application/octet-stream")Resume resume) {
		if (resume == null) {
			throw new NullPointerException("参数非法.");
		}

		DataHandler handler = resume.getDataHandler();
		if (handler == null) {
			throw new NullPointerException("参数非法.");
		}
		OutputStream os = null;
		InputStream is = null;
		try {
			is = handler.getInputStream();
			os = new FileOutputStream(new File("D:\\"
					+ resume.getCandidateName() + "."
					+ resume.getResumeFileType()));
			byte[] b = new byte[100000];
			int bytesRead = 0;
			while ((bytesRead = is.read(b)) != -1) {
				os.write(b, 0, bytesRead);
			}
			os.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (os != null) {
					os.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if (is != null) {
					is.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return "ok";
	}
}

其中关于文件传输采用二进制方式.

有2点是必须配置的。

1.传输对象中含有DataHandler 属性

@XmlMimeType("application/octet-stream")

private DataHandler dataHandler;

2.服务发布时协议规定

<!-- 文件传送必须协议 -->

<jaxws:properties>

<entry key="mtom-enabled" value="true"/>

</jaxws:properties>

spring配置

<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<!-- 设置密码bean -->
	<bean id="serverPasswordCallback" class="com.cxfdemo.ws.service.ServerPasswordCallback"></bean>

	<!-- WSS4J密码校验 -->
	<bean id="wss4jInInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
		<constructor-arg>
			<map>
				<!-- 用户认证(明文密码) -->
				<entry key="action" value="UsernameToken" />
				<entry key="passwordType" value="PasswordText" /><!-- 明文 密文采用PasswordDigest-->
				<entry key="passwordCallbackRef" value-ref="serverPasswordCallback" />
			</map>
		</constructor-arg>
	</bean>

	<!-- 发布服务 -->
	<jaxws:endpoint id="helloWorld" address="/helloWorld"
		implementor="com.cxfdemo.ws.service.HelloWorldService">
		<!-- 文件传送必须协议 -->
		<jaxws:properties>
        	<entry key="mtom-enabled" value="true"/>
		</jaxws:properties>
		<!-- 输入拦截器 -->
		<jaxws:inInterceptors>
			<ref bean="wss4jInInterceptor" />
			<!-- 日志打印 -->
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
		</jaxws:inInterceptors>
		<!-- 正常输出拦截器 -->
		<jaxws:outInterceptors>
			<bean class="com.cxfdemo.ws.service.interceptor.ErrorHandlerInterceptor"></bean>
		</jaxws:outInterceptors>
		<!-- 错误输出拦截器 -->
		<jaxws:outFaultInterceptors>
			<bean class="com.cxfdemo.ws.service.interceptor.ErrorHandlerInterceptor"></bean>
		</jaxws:outFaultInterceptors>
	</jaxws:endpoint>

	<jaxws:endpoint id="updateFile" address="/updateFile"
		implementor="com.cxfdemo.ws.service.UpdateFileService">
		<jaxws:properties>
        	<entry key="mtom-enabled" value="true" />
    	</jaxws:properties>
	</jaxws:endpoint>

	<!-- 全局配置 -->
	<!-- <cxf:bus>
		<cxf:features>
			<cxf:logging />
		</cxf:features>
	</cxf:bus> -->

采用的是jaxws:endpoint发布服务,至于其他方式,这里就不描述了,关于WSS4J的文章,网上也有很多。

服务端CallbackHandler配置

public class ServerPasswordCallback implements CallbackHandler {

    private static final Map<String, String> userMap = new HashMap<String, String>();

    static {
        userMap.put("client", "clientpass");
        userMap.put("server", "serverpass");
    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];
        //实际上 callback.getPassword()是为null
        String clientUsername = callback.getIdentifier();
        //其实这一步是应该从数据库中取密码在设置到callback中
        String serverPassword = userMap.get(clientUsername);
        if (serverPassword != null) {
            callback.setPassword(serverPassword);
        }

        try {
        	System.out.println(new Date());
        	Thread.sleep(1000L);
        } catch (InterruptedException e) {
        	//nothing.
        }
    }
}

网上很多文章中都是在这个handle里去校验密码是否匹配的,其实不然,你会发现 callback.getPassword()是为null的。

其实密码验证的工作是交给了WSS4JInInterceptor来处理的。

下面是一次错误密码的异常信息。

从异常堆栈中看出密码校验在UsernameTokenValidator中处理。源码如下。

 /**
     * Verify a UsernameToken containing a password digest. It does this by querying a
     * CallbackHandler instance to obtain a password for the given username, and then comparing
     * it against the received password.
     * @param usernameToken The UsernameToken instance to verify
     * @throws WSSecurityException on a failed authentication.
     */
    protected void verifyDigestPassword(UsernameToken usernameToken,
                                        RequestData data) throws WSSecurityException {
        if (data.getCallbackHandler() == null) {
            throw new WSSecurityException(WSSecurityException.FAILURE, "noCallback");
        }

        String user = usernameToken.getName();
        String password = usernameToken.getPassword();
        String nonce = usernameToken.getNonce();
        String createdTime = usernameToken.getCreated();
        String pwType = usernameToken.getPasswordType();
        boolean passwordsAreEncoded = usernameToken.getPasswordsAreEncoded();

        WSPasswordCallback pwCb =
            new WSPasswordCallback(user, null, pwType, WSPasswordCallback.USERNAME_TOKEN, data);
        try {
            data.getCallbackHandler().handle(new Callback[]{pwCb});
        } catch (IOException e) {
            if (log.isDebugEnabled()) {
                log.debug(e);
            }
            throw new WSSecurityException(
                WSSecurityException.FAILED_AUTHENTICATION, null, null, e
            );
        } catch (UnsupportedCallbackException e) {
            if (log.isDebugEnabled()) {
                log.debug(e);
            }
            throw new WSSecurityException(
                WSSecurityException.FAILED_AUTHENTICATION, null, null, e
            );
        }
        String origPassword = pwCb.getPassword();
        if (origPassword == null) {
            if (log.isDebugEnabled()) {
                log.debug("Callback supplied no password for: " + user);
            }
            throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
        }
        if (usernameToken.isHashed()) {
            String passDigest;
            if (passwordsAreEncoded) {
                passDigest = UsernameToken.doPasswordDigest(nonce, createdTime, Base64.decode(origPassword));
            } else {
                passDigest = UsernameToken.doPasswordDigest(nonce, createdTime, origPassword);
            }
            if (!passDigest.equals(password)) {
                throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
            }
        } else {
            if (!origPassword.equals(password)) {
                throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
            }
        }
    }

日志记录拦截器

public class ErrorHandlerInterceptor extends AbstractSoapInterceptor {

	public ErrorHandlerInterceptor() {
		super(Phase.MARSHAL);
	}

	public void handleMessage(SoapMessage message) throws Fault {
		// 错误原因
		Fault fault = (Fault) message.getContent(Exception.class);
		// 错误信息
		String errorMessage = null;
		Throwable cause = null;
		if (fault != null) {
			errorMessage = fault.getMessage();
			cause = fault.getCause();
		}

		Exchange exchange = message.getExchange();
		// wsdl描述
		String servicePath = null;
		// url与uri
		String url = null;
		String uri = null;
		// 客户端ip
		String clientIp = null;
		// 用户名
		String username = null;
		// 密码
		String psw = null;
		// 服务中的方法
		String methodName = null;
		// 参数名
		Object[] paramNames = null;
		// 参数值
		Object[] paramValues = null;

		if (exchange != null) {
			Object object = exchange.get("javax.xml.ws.wsdl.description");
			if (object != null) {
				servicePath = object.toString();
			}

			Message inMessage = exchange.getInMessage();
			if (inMessage != null) {
				HttpServletRequest req = (HttpServletRequest) inMessage
						.get("HTTP.REQUEST");
				clientIp = getIpAddr(req);
				url = (String) inMessage.get("org.apache.cxf.request.url");
				uri = (String) inMessage.get("org.apache.cxf.request.uri");
			}

			W3CDOMStreamWriter w3CDOMStreamWriter = (W3CDOMStreamWriter) inMessage
					.get(W3CDOMStreamWriter.class.getName());
			if (w3CDOMStreamWriter != null) {
				Document document = w3CDOMStreamWriter.getDocument();
				if (document != null) {
					NodeList usernames = document
							.getElementsByTagName("wsse:Username");
					NodeList psws = document
							.getElementsByTagName("wsse:Password");

					if (usernames != null && usernames.getLength() == 1) {
						username = usernames.item(0).getTextContent();
					}
					if (psws != null && psws.getLength() == 1) {
						psw = psws.item(0).getTextContent();
					}

					NodeList body = document.getElementsByTagName("soap:Body");
					Node method = body.item(0).getFirstChild();
					if (method != null) {
						methodName = method.getNodeName();
						String[] methods = methodName.split(":");
						if (methods != null && methods.length == 2) {
							methodName = methods[1];
						}

						NodeList args = method.getChildNodes();
						paramNames = getParam(args, true);
						paramValues = getParam(args, false);
					}
				}
			}
		}
		System.out.println(cause);
		System.out.println(errorMessage);
		System.out.println(servicePath);
		System.out.println(url);
		System.out.println(uri);
		System.out.println(clientIp);
		System.out.println(username);
		System.out.println(psw);
		System.out.println(methodName);
		System.out.println("---names--");
		System.out.print(getParam(paramNames) + "\t");
		System.out.println("\n---values--");
		System.out.print(getParam(paramValues) + "\t");
	}

其中拦截获取诸多信息,可作为日志记录入库,含错误原因、错误信息、wsdl描述、url与uri、客户端ip、用户名、密码、服务中的方法、 参数名和参数值

可根据是否含有错误原因或错误信息分辨出当前此次服务请求成功与否。

一次密码错误的日志信息

请求信息为:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" soap:mustUnderstand="1"><wsse:UsernameToken wsu:Id="UsernameToken-CF676781B956C77EB214127352680991"><wsse:Username>client</wsse:Username><wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">clientpass1</wsse:Password></wsse:UsernameToken></wsse:Security></SOAP-ENV:Header><soap:Body><ns2:saveUsers
xmlns:ns2="http://service.ws.cxfdemo.com/"><user><collections>11dfdfd</collections><collections>22dfdfd</collections><collections>33dfdfd</collections><id>id</id><map><entry><key>44</key><value>333</value></entry><entry><key>55</key><value>333</value></entry><entry><key>22</key><value>333</value></entry><entry><key>33</key><value>333</value></entry><entry><key>11</key><value>333</value></entry></map><name>name</name><sex>2</sex></user></ns2:saveUsers></soap:Body></soap:Envelope>

soapUI配置

Eclipse的插件地址:http://www.soapui.org/eclipse/update

客服端

采用wsdl2java 生成的客户端。

密码也是用WSS4JOutInterceptor拦截设置的。

public class ClientPasswordCallback implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback callback = (WSPasswordCallback) callbacks[0];
        System.out.println(new Date());
        try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
        callback.setPassword("clientpass");
    }
}

可以采用

spring配置

<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	<bean id="clientPasswordCallback" class="com.cxfdemo.ws.client.ClientPasswordCallback"></bean>

	<bean id="wss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
        <constructor-arg>
            <map>
                <!-- 用户认证(明文密码) -->
                <entry key="action" value="UsernameToken"/>
                <entry key="user" value="client"/>
                <entry key="passwordType" value="PasswordText"/>
                <entry key="passwordCallbackRef" value-ref="clientPasswordCallback"/>
            </map>
        </constructor-arg>
    </bean>

	<jaxws:client id="client" address="http://localhost:8888/CXFDemo/webservice/helloWorld"
		serviceClass="com.cxfdemo.ws.service.HelloWorld">
		<jaxws:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />
            <ref bean="wss4jOutInterceptor"/>
        </jaxws:outInterceptors>
	</jaxws:client>

	<jaxws:client id="updateFile" address="http://localhost:8888/CXFDemo/webservice/updateFile"
		serviceClass="com.cxfdemo.ws.service.HelloWorld">
	</jaxws:client>

	<!-- 对所有的服务配置超时机制   只对服务名为{http://service.ws.cxfdemo.com/}HelloWorldService的服务生效.   -->
	<http-conf:conduit name="*.http-conduit">
		<!-- ConnectionTimeout获取连接超时   ReceiveTimeout获取结果超时-->
		<http-conf:client ConnectionTimeout="15000" ReceiveTimeout="30000"/>
	</http-conf:conduit>

ApplicationContext context = new ClassPathXmlApplicationContext("client_Spring.xml");
        HelloWorld helloService = context.getBean("client",HelloWorld.class);
        String response = helloService.sayHi("Peter");
        System.out.println(response);

java代码控制

private static final QName SERVICE_NAME = new QName("http://service.ws.cxfdemo.com/", "HelloWorldServiceService");
URL wsdlURL = HelloWorldServiceService.WSDL_LOCATION;
	        if (args.length > 0 && args[0] != null && !"".equals(args[0])) {
	            File wsdlFile = new File(args[0]);
	            try {
	                if (wsdlFile.exists()) {
	                    wsdlURL = wsdlFile.toURI().toURL();
	                } else {
	                    wsdlURL = new URL(args[0]);
	                }
	            } catch (MalformedURLException e) {
	                e.printStackTrace();
	            }
	        }

        HelloWorldServiceService ss = new HelloWorldServiceService(wsdlURL, SERVICE_NAME);
        HelloWorld port = ss.getHelloWorldServicePort();
        org.apache.cxf.endpoint.Client client = ClientProxy.getClient(port);
        org.apache.cxf.endpoint.Endpoint cxfEndpoint = client.getEndpoint();
        Map<String, Object> outProps = new HashMap<String, Object>();
        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        outProps.put(WSHandlerConstants.USER, "client");
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
        outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordCallback.class.getName());
        WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(outProps);
        cxfEndpoint.getOutInterceptors().add(wssOut);

        System.out.println("Invoking sayHi...");
        java.lang.String _sayHi_text = "tttttt";
        java.lang.String _sayHi__return = port.sayHi(_sayHi_text);
        System.out.println("sayHi.result=" + _sayHi__return);
时间: 2024-10-14 01:01:54

cxf WebService整理的相关文章

CXF webService Map 适配器模式

CXF webservice 比JDK的实现强多了,直接支持了复杂类型,类,集合(先不说MAp),但是,Map的类型要支持的话,要自己写一个适配器. 其实不光是Map,有一些类CXF也不能直接支持,比如sql里面有个什么TimeStamp也要搞适配器. 1     写一个适配器,让他继承XmlAdapter<MyRole[], Map<String,List<Role>>>    其中前面是转换后的类型,后面是目标类型,也就是待转换的类型. package com.ja

CXF WebService整合SpringMVC的maven项目

首先推荐博客:http://www.cnblogs.com/xdp-gacl/p/4259481.html   http://blog.csdn.net/hu_shengyang/article/details/38384597 CXF是webService技术的一种实现工具,为什么用CXF来实现webService: 1.      Java的webService实现本身就是一个很耗性能的实现方案(xml与java对象之间在服务端以及客户端的互转比较消耗性能): 2.      目前java主

CXF webservice完整例子

Web Service技术应用广泛,可实现不同平台的数据交换,现做了一个CXF webservice小例子,供webservice初学者参考. 1.环境搭建 1.1  下载 Apache CXF  可以去官方下载 http://cxf.apache.org/ .也可点击http://pan.baidu.com/s/1jIPyOYU 我分享的云盘下载(apache-cxf-2.4.2).  解压文件 1.2  配置Apache CXF环境变量: 创建一个CXF_HOEM变量,值为CXF框架所在根目

java调用CXF WebService接口的两种方式

通过http://localhost:7002/card/services/HelloWorld?wsdl访问到xml如下,说明接口写对了. 2.静态调用 // 创建WebService客户端代理工厂        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();        // 判断是否抛出异常        factory.getOutInterceptors().add(new LoggingInIntercept

Cxf WebService实战

在前一篇转载的关于cxf webservice的实现思路中向大家介绍了实现webservice的思路,但并没有给出具体的实现代码,在本文中我将通过介绍借助于spring实现webservice的方式,在本文中同时将向大家介绍在我实现的过程中遇到的一些问题. 1.搭建webservice服务端: (1.1)新建java web工程,同时在web.xml中进行spring的相关配置,如下: <?xml version="1.0" encoding="UTF-8"?

使用spring和Tomcat发布CXF WebService

上一节中使用代理工厂JaxWsProxyFactoryBean来发布WebService, 这种方式必须指定运行的端口,如果端口被占用,就会发布失败. cxf的WebService也可利用Tomcat来发布,并且使用8080端口.方法如下: maven配置: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.version>4.3.0

cxf WebService设置wsdl中soapAction的值

用cxf开发一个WebService很简单,只需要下面几步: 1.定义接口 public interface HelloService { String hello(); } 2.实现 public class HelloServiceImpl implements HelloService { @Override public String hello() { return "hi,my name is gyoung "; } } 3.用ServerFactoryBean生成服务 p

CXF webservice远程调用接口

1.原始资料地址 http://www.360doc.com/content/14/0827/16/8790037_405134447.shtml http://blog.csdn.net/xzknet/article/details/17918343 2.CXF简介及所需要的jar包      Apache CXF是一个开源的WebService框架,CXF大大简化了Webservice的创建,同时它继承了XFire的传统,一样可以和spring天然的进行无缝的集成. CXF框架是一种基于se

WebService -- Java 实现之 CXF (WebService 服务器端接口)

1. 使用Maven创建一个quickstart项目 2. 引入依赖的Jar包 <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>3.1.5</version> </dependency> <dependency> <groupId>org.apache.cx