5、拦截器(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决定的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,269评论 19 139
  • 工作流程 一次HTTP操作称为一个事务,其工作过程可分为四步: 1)首先客户机与服务器需要建立连接。只要单击某个超...
    保川阅读 4,656评论 2 14
  • 晓宇正在等电梯,电梯门开了,一个年长的女人躺在病床上正往出推。 晓宇瞥了一眼,觉得眼熟,但一时想不起是谁。身边陪护...
    天空有云阅读 739评论 0 15
  • 微信号:梅蓓 一、分段提炼: 1.格拉德认为需要持续不断的努力,经过一万小时锤炼方可达到超凡; 2.你以为只要练习...
    梅子Mey阅读 227评论 1 0
  • 1.让沟通更高效:钉钉、美洽 2.让协作更快捷:今目标、石墨、Worktile/Teambition 3.让你的演...
    薏米水阅读 2,155评论 2 38