WebService拦截器深度讲解与实战演练

一、CXF拦截器

为了让程序员能访问、并修改CXF框架所生成的SOAP消息,CXF提供了拦截器。
拦截器可以加载服务器端,也可以加载客户端,而拦截器分为In拦截器和Out拦截器。

1.1、对于在服务器端添加拦截器(Inter_Server工程)

MyService.java

package org.fkjava.cxf.ws.server;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import javax.xml.ws.Endpoint;import org.apache.cxf.interceptor.LoggingInInterceptor;import org.apache.cxf.interceptor.LoggingOutInterceptor;import org.apache.cxf.jaxws.EndpointImpl;import org.fkjava.cxf.ws.HelloWorld;import org.fkjava.cxf.ws.impl.HelloWorldWs;public class MyService {
public static void main(String[] args) throws IOException {

HelloWorld hw = new HelloWorldWs();

//调用此方法发布WebService

EndpointImpl ep = (EndpointImpl)Endpoint.publish("http://localhost:8080/myService", hw);

ep.getInInterceptors().add(new LoggingInInterceptor(new PrintWriter(new FileWriter("in.txt"))));//添加In拦截器

ep.getOutInterceptors().add(new LoggingOutInterceptor(new PrintWriter(new FileWriter("out.txt"))));//添加Out拦截器

System.out.println("Hello World!");
}}

说明:

  • (1)首先获取Endpointpublish方法的返回值;
  • (2)调用该方法的返回值的getInInterceptorgetOutInterceptor方法来获取In、Out拦截器列表,接下来就可以添加拦截器了。
  • (3)这里我们是使用的CXF给我们定义好的拦截器,但是这个拦截器在API文档中是查不到的,我们将相关输入和输出信息输出到文件中。添加的拦截器中(如LoggingInInterceptor)如果不给参数,则相关数据打印在控制台,这里我们让其输出到相关的文件中去。

注意:这里如果不想太麻烦,直接加入所有的依赖包。同时客户端不需要改变,因为我们需要的操作没有改变。

1.2 测试

这里我们使用工程WS_Client03对上面才改造服务端进行测试。在输出的文件中我们可以看到一些信息。在测试方法中我们总共使用的3个操作,于是应该有6个SOAP消息,从输出的文件中我们确实可以看到6个SOAP消息,在in.txt文件中前面两条先不管。我们从中截取出一段出来进行分析:
in.txt

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>

<ns2:sayHi xmlns:ns2="http://ws.cxf.fkjava.org/">


<arg0>张三</arg0>

</ns2:sayHi>
</soap:Body></soap:Envelope>

out.txt

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>

<ns2:sayHiResponse xmlns:ns2="http://ws.cxf.fkjava.org/">


<return>张三您好!现在的时间是: Mon Jun 27 21:18:11 CST 2016</return>

</ns2:sayHiResponse>
</soap:Body></soap:Envelope>

说明:从这里我们可以看出SOAP消息的结构

  • SOAP信息的根元素是Envelope,此元素包含两个子元素:Header(默认情况下没有,即不是强制出现的,其是由程序员控制添加,主要用于携带一些额外的信息,如用户名和密码)和Body
  • 对于Body子元素:(1)如果调用正确,其内容应该遵守wsdl所要求的格式;(2)如果调用错误(比如我们使用http://localhost:8080/myService调用,不指明服务名),其内容就是Fault子元素。

1.3 在客户端添加拦截器(工程Inter_Client

客户端的做法和服务端的做法基本一样。
MyClient.java

package org.fkjava.cxf.ws.client;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import java.util.List;import org.apache.cxf.frontend.ClientProxy;import org.apache.cxf.interceptor.LoggingInInterceptor;import org.apache.cxf.interceptor.LoggingOutInterceptor;import org.apache.cxf.endpoint.Client;import org.fkjava.cxf.ws.Cat;import org.fkjava.cxf.ws.Entry;import org.fkjava.cxf.ws.HelloWorld;import org.fkjava.cxf.ws.StringCat;import org.fkjava.cxf.ws.User;import org.fkjava.cxf.ws.impl.HelloWorldWs;public class MyClient {
public static void main(String[] args) throws IOException {

HelloWorldWs factory = new HelloWorldWs();

HelloWorld hw = factory.getHelloWorldWsPort();



Client client = ClientProxy.getClient(hw) ;//调用此方法,以远程WebService的代理为参数

client.getInInterceptors().add(new LoggingInInterceptor(new PrintWriter(new FileWriter("in.txt"))));

client.getOutInterceptors().add(new LoggingOutInterceptor(new PrintWriter(new FileWriter("out.txt"))));

System.out.println(hw.sayHi("张三"));



User user = new User();

user.setId(30);//只要名字和密码相同则认为是同一个用户,所以这里给30没关系

user.setName("大熊");

user.setPassword("111");



List<Cat> cats = hw.getCatsByUser(user);

for(Cat cat : cats){


System.out.println(cat.getName());

}



StringCat sc = hw.getAllCats();

for(Entry entry : sc.getEntries()){


System.out.println(entry.getKey() + entry.getValue().getName());




}
}}

说明:首先我们使用ClientProxy类得到Client,之后就可以通过此类得到相关的拦截器列表了。同样测试之后我们也可以在相关的输出文件中看到输出的SOAP消息。

二、自定义拦截器(工程Auth_Server

2.1服务端

这里我们是要自定义一个拦截器来进行用户名和密码的检查,于是只需要在服务器端加一个In拦截器即可。

MyServer.java

package org.fkjava.cxf.ws.server;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import javax.xml.ws.Endpoint;import org.apache.cxf.interceptor.LoggingInInterceptor;import org.apache.cxf.interceptor.LoggingOutInterceptor;import org.apache.cxf.jaxws.EndpointImpl;import org.fkjava.cxf.ws.HelloWorld;import org.fkjava.cxf.ws.auth.AuthInterceptor;import org.fkjava.cxf.ws.impl.HelloWorldWs;public class MyServer {
public static void main(String[] args) throws IOException {

HelloWorld hw = new HelloWorldWs();

//添加一个自定义的In拦截器,负责检查用户和密码

EndpointImpl ep = (EndpointImpl)Endpoint.publish("http://localhost:8080/myService", hw);

ep.getInInterceptors().add(new AuthInterceptor());//添加In拦截器

System.out.println("Hello World!");
}}

说明:这里的AuthInterceptor就是我们自定义的拦截器。我们需要实现Interceptor接口,而实际上,我们一般会继承AbstractPhaseInterceptor
AuthInterceptor.java

package org.fkjava.cxf.ws.auth;import org.apache.cxf.binding.soap.SoapMessage;import org.apache.cxf.interceptor.Fault;import org.apache.cxf.phase.AbstractPhaseInterceptor;import org.apache.cxf.phase.Phase;//通过PhaseInterceptor指定拦截器在哪个阶段起作用public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

public AuthInterceptor() {

//下面的常量表示在调用之前让拦截器起作用

super(Phase.PRE_INVOKE);//显式调用父类有参构造器,因为AbstractPhaseInterceptor没有无参构造器
}
//实现自己的拦截器的时候需要实现此方法,其中的形参就是被拦截到的SOAP消息
@Override
public void handleMessage(SoapMessage msg) throws Fault {

System.out.println("********" + msg);//从这里可以看到已经拦截到了SOAP消息
}}

说明:这里我们需要实现handleMessage方法,同时由于AbstractPhaseInterceptor抽象类没有无参构造函数,所以我们必须显式调用有参构造函数。其中参数表示让拦截器起作用的阶段,这里是调用之前拦截。拦截到的就是SOAP消息,之后我们需要修改和解析消息,这里我们先验证是否拦截到了SOAP消息,我们可以使用客户端进行访问来测试。会打印出相应的的消息。

解析SOAP消息:
AuthInterceptor.java

package org.fkjava.cxf.ws.auth;import java.util.List;import org.apache.cxf.binding.soap.SoapMessage;import org.apache.cxf.headers.Header;import org.apache.cxf.interceptor.Fault;import org.apache.cxf.phase.AbstractPhaseInterceptor;import org.apache.cxf.phase.Phase;import org.w3c.dom.Element;import org.w3c.dom.NodeList;//通过PhaseInterceptor指定拦截器在哪个阶段起作用public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

public AuthInterceptor() {

//下面的常量表示在调用之前让拦截器起作用

super(Phase.PRE_INVOKE);//显式调用父类有参构造器,因为AbstractPhaseInterceptor没有无参构造器
}
//实现自己的拦截器的时候需要实现此方法,其中的形参就是被拦截到的SOAP消息
@Override
public void handleMessage(SoapMessage msg) throws Fault {

System.out.println("********" + msg);//从这里可以看到已经拦截到了SOAP消息

List<Header> headers = msg.getHeaders();//得到SOAP的所有HEADER

//如果没有HEADER

if(headers == null || headers.size() < 1){


throw new Fault(new IllegalArgumentException("没有头信息"));

}

//加入要求第一个HEADER中携带了用户名和密码信息

Header firstHeader = headers.get(0);//得到第一个HEADER

Element ele = (Element) firstHeader.getObject();//得到HEADER的内容

NodeList usernames = (NodeList) ele.getElementsByTagName("username");//我们要求有一个username的标签

NodeList passwords = (NodeList) ele.getElementsByTagName("password");

if(usernames.getLength() != 1){


throw new Fault(new IllegalArgumentException("用户名的格式不对"));

}

if(passwords.getLength() != 1){


throw new Fault(new IllegalArgumentException("密码的格式不对"));

}

String username = usernames.item(0).getTextContent();//得到第一个userId元素里的文本内容,以该内容作为用户名

String password = passwords.item(0).getTextContent();

//实际项目中应该去查询数据库,检查该用户名密码是否能够被授权

if(!(username.equals("大熊") && password.equals("111"))){


throw new Fault(new IllegalArgumentException("用户名或密码不正确"));

}

//放行。。。这里不需要我们管
}}

说明:

  • 1.首先我们得到一个Header列表。然后进行判断,如果确实有Header元素则我们取出列表中的第一个Header,因为会有很多Header,而这里其实只有一个,但是我们认为我们的用户名和密码信息放在第一个Header中。
  • 2.下面我们需要从头中解析出用户名和密码,当然用户名和密码信息所在的元素肯定是和客户端设置的一致,解析出来判断之后我们只需放行即可。

2.2 客户端(工程Auth_Client

MyClient.java

package org.fkjava.cxf.ws.client;import java.io.FileWriter;import java.io.IOException;import java.io.PrintWriter;import java.util.List;import org.apache.cxf.frontend.ClientProxy;import org.apache.cxf.interceptor.LoggingInInterceptor;import org.apache.cxf.interceptor.LoggingOutInterceptor;import org.apache.cxf.endpoint.Client;import org.fkjava.cxf.ws.Cat;import org.fkjava.cxf.ws.Entry;import org.fkjava.cxf.ws.HelloWorld;import org.fkjava.cxf.ws.StringCat;import org.fkjava.cxf.ws.User;import org.fkjava.cxf.ws.auth.AddHeaderInterceptor;import org.fkjava.cxf.ws.impl.HelloWorldWs;public class MyClient {
public static void main(String[] args) throws IOException {

HelloWorldWs factory = new HelloWorldWs();

HelloWorld hw = factory.getHelloWorldWsPort();



Client client = ClientProxy.getClient(hw) ;//调用此方法,以远程WebService的代理为参数

client.getOutInterceptors().add(new AddHeaderInterceptor("大熊", "111"));

client.getOutInterceptors().add(new LoggingOutInterceptor());

System.out.println(hw.sayHi("张三"));



User user = new User();

user.setId(30);//只要名字和密码相同则认为是同一个用户,所以这里给30没关系

user.setName("大熊");

user.setPassword("111");



List<Cat> cats = hw.getCatsByUser(user);

for(Cat cat : cats){


System.out.println(cat.getName());

}



StringCat sc = hw.getAllCats();

for(Entry entry : sc.getEntries()){


System.out.println(entry.getKey() + entry.getValue().getName());




}
}}

说明:客户端我们是加一个Out拦截器,即在输出SOAP消息的时候使用拦截器加上用户名和密码等信息头,这里我们还使用了一个CXF提供的拦截器主要是为了便于通过控制台查看一些信息。

自定义拦截器:
AddHeaderInterceptor.java

package org.fkjava.cxf.ws.auth;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;public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

private String username ;
private String password;

public AddHeaderInterceptor(String username, String password) {

super(Phase.PREPARE_SEND);//这里表示准备发送SOAP消息的时候调用此拦截器

this.username = username;

this.password = password;


}
@Override
public void handleMessage(SoapMessage msg) throws Fault {

List<Header> headers = msg.getHeaders();

Document document = DOMUtils.createDocument();//创建一个Document对象



Element ele = document.createElement("authHeader");//创建一个元素,这个名字随便

Element usernameEle = document.createElement("username");//创建一个元素,注意和服务端元素名字一致



usernameEle.setTextContent(username);//将相关的值设置进去

Element passwordEle = document.createElement("password");//创建一个元素

passwordEle.setTextContent(password);



ele.appendChild(usernameEle);

ele.appendChild(passwordEle);

//生成了一个如下的代码片段

/*
  <authHeader>

 *

  <username>username</username>

 *

  </password>password</password>

 *
  </authHeader>

 * */

//把ele元素包装成Header类,然后添加到SOAP消息的Header列表中

headers.add(new Header(new QName("fkjava"), ele));//这里的QName参数值随便设置
}}

说明:当然拦截器中我们肯定要将用户名和密码传递进去。和服务端的实现方式是一样的,只是客户端是需要添加xml代码片段。测试之后我们发现在控制台可以看到这样的信息:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>

<authHeader>


<username>大熊</username>


<password>111</password>

</authHeader>
</soap:Header>
<soap:Body>

<ns2:sayHi xmlns:ns2="http://ws.cxf.fkjava.org/">


<arg0>张三</arg0>

</ns2:sayHi>
</soap:Body></soap:Envelope>

说明:这样便成功了。而<soap:Body>中的内容不受我们控制,是由WSDL决定的。

未经允许不得转载:大自然的搬运工 » WebService拦截器深度讲解与实战演练

赞 (0)