最近项目中用到了Web service,不愿意使用wsdl2java的方式,完全xml生成soap信封。
自己架设了WS服务及客户端测试,并实现了username token认证
java有三个WS规范,分别为JAXM&SAAJ、 JAX-WS (JAX-RPC)、 JAX-RS
本例属于JAX-WS
关于服务:使用Person类作为参数,将Person更改后返回给client。分别实现一个简单的WS及一个带username token验证的WS。
关于客户端:两种方式实现WS:Service.Mode.PAYLOAD及Service.Mode.MESSAGE。这两种方式都为发送纯粹xml机制,不用关注wsdl到javabean的转换。PAYLOAD只用关心soap的信封体(body),MESSAGE要实现soap信封(header and body),并且MESSAGE增加了username token认证。
准备工作:
1、到官网http://cxf.apache.org/,下载最新版本,我下载时最新为apache-cxf-2.2.9.zip,大概32M
2、解压,我打开了两个eclipse,分别建工程,一个为server一个为client,以便测试用,并分别引入cxf文件夹下的lib目录下的所有jar
下面是代码
服务器端:
定义一个WS接口
package test.service;
import javax.jws.WebService;
@WebService
public interface IHello {
Person getPerson(Person child);
}
编写一个实限类
package test.service;
public class Hello implements IHello {
public Person getPerson(Person child) {
Person p=new Person();
p.setAge(child.getAge()+20);
p.setUserName(child.getUserName()+"'s father.");
return p;
}
}
是不是缺少一个Person?那就创建吧
package test.service;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="PersonName")
public class Person {
private String userName=null;
private int age=0;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
好了,完事具备,只欠启动了,写一个main类吧
package test.service;
import java.util.HashMap;
import java.util.Map;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;
public class Server {
public static void main(String[] args) {
Server server=new Server();
server.test(); //简单的不带usernametoken的WS
//server.testUserNameToken();// 启用usernametoken的WS
}
public void test(){
EndpointImpl.publish("http://127.0.0.1:8888/helloServer", new Hello());
}
private WSS4JInInterceptor getInInterceptor(){
Map<String,Object> inProps= new HashMap<String,Object>();
inProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// 密码类型 : plain text
inProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// 客户信息校验类设置
inProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ServerPasswordCallback.class.getName());
return new WSS4JInInterceptor(inProps);
}
private WSS4JOutInterceptor getOutInterceptor(){
Map<String,Object> outProps = new HashMap<String,Object>();
outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
// 服务器用户标识
outProps.put(WSHandlerConstants.USER, "IamServer");
// 密码类型 : plain text
outProps.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
// 返回给客户端的密码信息
outProps.put(WSHandlerConstants.PW_CALLBACK_CLASS, ClientPasswordCallback.class.getName());
return new WSS4JOutInterceptor(outProps);
}
public void testUserNameToken(){
EndpointImpl jaxWsEndpoint = (EndpointImpl) EndpointImpl.publish("http://127.0.0.1:8888/helloServer", new Hello());
Endpoint cxfEndpoint = jaxWsEndpoint.getServer().getEndpoint();
cxfEndpoint.getInInterceptors().add(getInInterceptor());
//cxfEndpoint.getInInterceptors().add(new SAAJInInterceptor()); // 2.0.x 需要该句; 2.1及以上不需要
cxfEndpoint.getOutInterceptors().add(getOutInterceptor());
//cxfEndpoint.getOutInterceptors().add(new SAAJOutInterceptor()); // 2.0.x 需要该句; 2.1及以上不需要
}
}
说明:上面过程创建了一个服务类,其实是两个服务,测试时在main方法里分别启用
另外用到两个身份验证的CallbackHandler ,分别实现如下
package test.service;
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 ServerPasswordCallback implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
WSPasswordCallback pwcb = (WSPasswordCallback) callbacks[0];
String id = pwcb.getIdentifier();
switch (pwcb.getUsage()) {
case WSPasswordCallback.USERNAME_TOKEN_UNKNOWN:
// 密码方式 plaintext
if (!"leezuu".equals(id)) {
throw new UnsupportedCallbackException(pwcb,
"username is invalid.");
}
if (!"mima".equals(pwcb.getPassword())) {
throw new UnsupportedCallbackException(pwcb,
"password is invalid.");
}
break;
case WSPasswordCallback.DECRYPT:
case WSPasswordCallback.SIGNATURE:
// used to retrieve password for private key
if ("serverkey".equals(id)) {
pwcb.setPassword("serverpass");
}
break;
}
}
}
package test.service;
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 {
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
//服务器标识
System.out.println("Server ID:"+pc.getIdentifier());
// 设置返回给客户的服务器密码信息
pc.setPassword("sssss");
}
}
可能要问,WS不是运行在WEB容器里的吗?上面的 EndpointImpl.publish("http://127.0.0.1:8888/helloServer", new Hello());一句话就可以启动一个WS?
对的,WS需要容器,CXF下载的包中其实已经包括了一个,那就是Jetty,虽然没有tomcat,但其实还是有容器的。
ok,运行下,看到许多输出
打开IE,输入地址查看wsdl,http://127.0.0.1:8888/helloServer?wsdl
这样一个服务就已经发布了,可以根据需要实现相应的client,下面是一个实现,采用xml的方式,不用javabean。