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的。
/** * 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、用户名、密码、服务中的方法、 参数名和参数值
soapUI配置
客服端
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);
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。