Spring Boot & Apache CXF——简单的webservice,并实现用户验证

最近项目中需要开发webservice对外提供服务,选用框架时主要考虑Axis和CXF。Axis跨语言是它的一大特点,但因为主程序是Spring Boot开发,CXF可以与它无缝对接。所以选用了CXF框架。在网上扒拉了许久,大多都是和Spring集成的例子,通过一番折腾,终于实现了Spring Boot和CXF的集成完成了任务的开发,在此坐下笔记。

一、添加依赖

       项目是用gradle来管理依赖的,gradle文件如下:

group 'Test_CXF_Webservice'
version '1.0.1'

apply plugin: 'java'
apply plugin: 'spring-boot'

sourceCompatibility = 1.7
targetCompatibility = 1.7

repositories {
    mavenCentral()
}


ext {
    springBootVersion = "1.3.6.RELEASE"
    springVersion = "4.2.7.RELEASE"
    springSecurityVersion = "4.0.4.RELEASE"
    cxfVersion = "3.1.7"
}


buildscript {
    ext {
        springBootVersion = "1.3.6.RELEASE"
    }

    // 这里主要添加spring-boot的插件的仓库
    repositories {
        maven { url "https://repo.spring.io/libs-release" }
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}


dependencies {
    compile (
            "org.springframework.boot:spring-boot-starter-web:${springBootVersion}",
            "org.apache.cxf:cxf-spring-boot-starter-jaxws:${cxfVersion}",
    )

    testCompile group: 'junit', name: 'junit', version: '4.11'
}

springBoot {
    mainClass="com.webservice.WSApplication"
}

二、服务端

2.1、service
package com.webservice.service;

import com.webservice.bean.OperationResult;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService(name = "LayoutService", targetNamespace = "http://service.webservice.com/")
public interface Layout {

    @WebMethod
    String sayHello(@WebParam(name = "name") String name);

    @WebMethod
    OperationResult addLayout(@WebParam(name = "layoutName")    String layoutName,
                              @WebParam(name = "layoutContent") String layoutContent);
}

这里实现了两个方法,其中第二个方法用于返回一个类对象。targetNamespace一般为包名的逆序。
package com.webservice.bean;

import java.io.Serializable;

public class OperationResult implements Serializable {
    private static final long serialVersionUID = -5939599230753662529L;

    private boolean succeed;
    private String msg;

    public OperationResult() {
    }

    public OperationResult(boolean succeed, String msg) {
        this.succeed = succeed;
        this.msg = msg;
    }

   //set get

    @Override
    public String toString() {
        return "OperationResult{" +
                "succeed=" + succeed +
                ", msg='" + msg + '\'' +
                '}';
    }
}
2.2、实现
package com.webservice.service;

import com.webservice.bean.OperationResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

@javax.jws.WebService(
        serviceName = "LayoutServiceImpl", portName = "LayoutImpl",
        targetNamespace = "http://service.webservice.com/",
        endpointInterface = "com.webservice.service.Layout")
public class LayoutImpl implements Layout {
    private static final Logger LOG = LoggerFactory.getLogger(LayoutImpl.class);

    @Override
    public String sayHello(String name) {
        return "hello," + name;
    }

    @Override
    public OperationResult addLayout(String layoutName, String layoutContent) {
        LOG.info("layoutName:{}, layoutContent:{}", layoutName, layoutContent);
        if (StringUtils.isEmpty(layoutName) || StringUtils.isEmpty(layoutContent)) {
            return new OperationResult(false, "参数不能为空");
        }

        //TODO
        return new OperationResult(true, null);
    }
}
2.3、定义拦截器用于用户验证
package com.webservice.interceptor;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.saaj.SAAJInInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.NodeList;

import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;

public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage>{
	private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
	private SAAJInInterceptor saa = new SAAJInInterceptor();

    private static final String USER_NAME = "admin";
	private static final String USER_PASSWORD = "pass";

    public AuthInterceptor() {
		super(Phase.PRE_PROTOCOL);
		getAfter().add(SAAJInInterceptor.class.getName());
	}

	@Override
	public void handleMessage(SoapMessage message) throws Fault {
		SOAPMessage mess = message.getContent(SOAPMessage.class);
        if (mess == null) {
            saa.handleMessage(message);
            mess = message.getContent(SOAPMessage.class);
        }
        SOAPHeader head = null;
        try {
            head = mess.getSOAPHeader();
        } catch (Exception e) {
            logger.error("getSOAPHeader error: {}",e.getMessage(),e);
        }
        if (head == null) {
            throw new Fault(new IllegalArgumentException("找不到Header,无法验证用户信息"));
        }

        NodeList users = head.getElementsByTagName("username");
        NodeList passwords = head.getElementsByTagName("password");
        if (users.getLength() < 1) {
            throw new Fault(new IllegalArgumentException("找不到用户信息"));
        }
        if (passwords.getLength() < 1) {
            throw new Fault(new IllegalArgumentException("找不到密码信息"));
        }

        String userName = users.item(0).getTextContent().trim();
        String password = passwords.item(0).getTextContent().trim();
        if(USER_NAME.equals(userName) && USER_PASSWORD.equals(password)){
            logger.debug("admin auth success");
        } else {
            SOAPException soapExc = new SOAPException("认证错误");
            logger.debug("admin auth failed");
            throw new Fault(soapExc);
        }
    }
}
 Interceptor是CXF架构中一个很有特色的模式。你可以在不对核心模块进行修改的情况下,动态添加很多功能。这对于CXF这个以处理消息为中心的服务框架来说是非常有用的,CXF通过在Interceptor中对消息进行特殊处理,实现了很多重要功能模块。这里就是采用拦截器进行用户验证。
   2.4 配置
    Spring Boot省去了诸多的XML配置,具体配置如下
package com.webservice.config;

import com.webservice.interceptor.AuthInterceptor;
import com.webservice.service.LayoutImpl;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.xml.ws.Endpoint;

@Configuration
public class CXFConfig {
    @Bean(name = Bus.DEFAULT_BUS_ID)
    public SpringBus springBus() {
        return new SpringBus();
    }

    @Bean
    public LayoutImpl layout() {
        return new LayoutImpl();
    }

    @Bean
    public Endpoint endpoint() {
        EndpointImpl endpoint = new EndpointImpl(springBus(), layout());
        endpoint.publish("/layout");
        endpoint.getInInterceptors().add(new AuthInterceptor());
        return endpoint;
    }
}
  其中,Spring Boot已帮你自动注册了servlet,默认为 /services,如果需要修改(如修改成webservice),两个方式:
  方式一:通过配置文件,在resources下新建application.properties文件
  cxf.path=/webservice
  方式二:通过代码,在CXFConfig中添加以下代码:
@Bean
    public ServletRegistrationBean servletRegistrationBean() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new CXFServlet(), "/webservice/*");
        bean.setLoadOnStartup(0);
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
     2.5、主程序入口
package com.webservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

@SpringBootApplication
public class WSApplication extends SpringBootServletInitializer {
    //可在在servlet中部署的war包
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(WSApplication.class);
    }


    public static void main(String[] args) throws Exception {
        SpringApplication.run(WSApplication.class, args);
    }
}
至此,服务端就完成了。
运行后,输入 http://localhost:8080/services/(使用默认配置),如下图

 点击并查看wsdl,如下
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://service.webservice.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="LayoutServiceImpl" targetNamespace="http://service.webservice.com/">
  <wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://service.webservice.com/" elementFormDefault="unqualified" targetNamespace="http://service.webservice.com/" version="1.0">

  <xs:element name="addLayout" type="tns:addLayout"/>

  <xs:element name="addLayoutResponse" type="tns:addLayoutResponse"/>

  <xs:element name="sayHello" type="tns:sayHello"/>

  <xs:element name="sayHelloResponse" type="tns:sayHelloResponse"/>

  <xs:complexType name="addLayout">
    <xs:sequence>
      <xs:element minOccurs="0" name="layoutName" type="xs:string"/>
      <xs:element minOccurs="0" name="layoutContent" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="addLayoutResponse">
    <xs:sequence>
      <xs:element minOccurs="0" name="return" type="tns:operationResult"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="operationResult">
    <xs:sequence>
      <xs:element minOccurs="0" name="msg" type="xs:string"/>
      <xs:element name="succeed" type="xs:boolean"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="sayHello">
    <xs:sequence>
      <xs:element minOccurs="0" name="name" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="sayHelloResponse">
    <xs:sequence>
      <xs:element minOccurs="0" name="return" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>
  </wsdl:types>
  <wsdl:message name="addLayout">
    <wsdl:part element="tns:addLayout" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="addLayoutResponse">
    <wsdl:part element="tns:addLayoutResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="sayHelloResponse">
    <wsdl:part element="tns:sayHelloResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="sayHello">
    <wsdl:part element="tns:sayHello" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="LayoutService">
    <wsdl:operation name="addLayout">
      <wsdl:input message="tns:addLayout" name="addLayout">
    </wsdl:input>
      <wsdl:output message="tns:addLayoutResponse" name="addLayoutResponse">
    </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="sayHello">
      <wsdl:input message="tns:sayHello" name="sayHello">
    </wsdl:input>
      <wsdl:output message="tns:sayHelloResponse" name="sayHelloResponse">
    </wsdl:output>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="LayoutServiceImplSoapBinding" type="tns:LayoutService">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="addLayout">
      <soap:operation soapAction="" style="document"/>
      <wsdl:input name="addLayout">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="addLayoutResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
    <wsdl:operation name="sayHello">
      <soap:operation soapAction="" style="document"/>
      <wsdl:input name="sayHello">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="sayHelloResponse">
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="LayoutServiceImpl">
    <wsdl:port binding="tns:LayoutServiceImplSoapBinding" name="LayoutImpl">
      <soap:address location="http://localhost:8080/services/layout"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>




通过此wdsl,编写客户端代码。

三、客户端

客户端可以通过两种方式调用服务端。
3.1、通过JaxWsDynamicClientFactory方式。
public class WSClient {
    private static final Logger LOG = LoggerFactory.getLogger(WSClient.class);

    private static final String USER_NAME = "admin";
    private static final String PASS_WORD = "pass";

    public static void main(String[] args) {
        //方式一:通过JaxWsDynamicClientFactory方式
        JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
        Client client = dcf.createClient("http://localhost:8080/services/layout?wsdl");
        client.getOutInterceptors().add(new ClientLoginInterceptor(USER_NAME, PASS_WORD));
        Object[] objects = new Object[0];
        try {
            objects = client.invoke("sayHello", "jack");
        } catch (java.lang.Exception e) {
            e.printStackTrace();
            LOG.error("[1]sayHello:{}", e.getMessage());
        }

        if (!ObjectUtils.isEmpty(objects)) {
            LOG.debug("[1]sayHello result_class:" + objects[0].getClass());
            LOG.debug("[1]sayHello result_msg:" + objects[0].toString());
        } else {
            LOG.debug("[1]getName result_null");
        }

        try {
            objects = client.invoke("addLayout", new Object[] {"name", "content"});
        } catch (java.lang.Exception e) {
            e.printStackTrace();
            LOG.error("[1]addLayout:{}", e.getMessage());
        }
        if (!ObjectUtils.isEmpty(objects)) {
            LOG.debug("[1]addLayout result_class:" + objects[0].getClass());
            OperationResult result = (OperationResult) objects[0];
            LOG.debug("[1]addLayout result_succeed:" + result.isSucceed());
            LOG.debug("[1]addLayout result_msg:" + result.getMsg());
        } else {
            LOG.debug("[1]addLayout result_null");
        }
}

这种方式要注意的就是,如果调用的服务接口返回的是一个自定义对象,那么结果Object[]中的数据类型就成了这个自定义对象(组件帮你自动生成了这个对象),

但是你本地可能并没有这个类,所以需要自行转换处理,最简单的是新建一个跟返回结果一模一样的类进行强转

客户端需要在header中添加用户信息,这样才能通过服务器的验证。
package com.webservice.interceptor;

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;

import javax.xml.namespace.QName;
import java.util.List;

public class ClientLoginInterceptor  extends AbstractPhaseInterceptor<SoapMessage> {

    private String username;
    private String password;

    public ClientLoginInterceptor(String username, String password) {
        super(Phase.PREPARE_SEND);
        this.username = username;
        this.password = password;
    }

    @Override
    public void handleMessage(SoapMessage soap) throws Fault {
        List<Header> headers = soap.getHeaders();
        Document doc = DOMUtils.createDocument();
        Element auth = doc.createElement("authrity");
        Element username = doc.createElement("username");
        Element password = doc.createElement("password");
        username.setTextContent(this.username);
        password.setTextContent(this.password);
        auth.appendChild(username);
        auth.appendChild(password);
        headers.add(0, new Header(new QName("tiamaes"),auth));
    }
}
3.2、通过wsimport生成客户端代码
通过jdk自带的wsimport生成客户端代码。进入$JAVA_HOME/bin下,新建bin和src两个文件夹,执行以下命令:
wsimport -d ./bin -s ./src -keep http://localhost:8080/services/layout?wsdl
其中几个参数有以下几个,
-d:生成客户端执行类的class文件存放目录,
-s:生成客户端执行类的源文件存放目录,
-p:定义生成类的包名
  -verbose:显示生成过程
需要注意的是: 无论-d或是-s后的目录必须在文件系统中存在,否则报错。

        将src下连同包一起复制到程序中,编写客户端入口类:
       
public class WSClient {
    private static final Logger LOG = LoggerFactory.getLogger(WSClient.class);

    private static final String USER_NAME = "admin";
    private static final String PASS_WORD = "pass";

    public static void main(String[] args) {
        
        //方式二:通过wsimport生成客户端代码
        LayoutServiceImpl impl = new LayoutServiceImpl();
        impl.setHandlerResolver(new HandlerResolver() {
            @Override
            public List<Handler> getHandlerChain(PortInfo portInfo) {
                List<Handler> handlerList = new ArrayList<Handler>();
                handlerList.add(new ClientHandler(USER_NAME, PASS_WORD));
                return handlerList;
            }
        });
        try {
            LayoutService layoutService = impl.getLayoutImpl();
            String result_1 = layoutService.sayHello("jack");
            LOG.debug("[2]sayHello:" + result_1);
            OperationResult result_2 = layoutService.addLayout("name", "content");
            LOG.debug("[2]addLayout result_succeed:" + result_2.isSucceed());
            LOG.debug("[2]addLayout result_msg:" + result_2.getMsg());
        } catch (SOAPFaultException e) {
            LOG.error("SOAPFaultException occurs:{}", e.getMessage());
        }
    }
}
 该方式的好处是可以像调用本地接口一样调用服务端方法,简单明了。缺点就是会生成一堆文件。
 用户的头信息设置类:
package com.webservice.interceptor;

import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.util.Set;

public class ClientHandler implements SOAPHandler<SOAPMessageContext> {
    private String username;
    private String password;

    public ClientHandler(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public boolean handleMessage(SOAPMessageContext ctx) {

        //出站,即客户端发出请求前,添加表头信息
        Boolean request_p = (Boolean)ctx.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
        if (request_p) {
            try {
                SOAPMessage msg = ctx.getMessage();
                SOAPEnvelope env = msg.getSOAPPart().getEnvelope();
                SOAPHeader hdr = env.getHeader();
                if (hdr == null) hdr = env.addHeader();

                //添加认证信息头
                QName name = new QName("http://service.webservice.com/", "LayoutImpl");
                SOAPHeaderElement header = hdr.addHeaderElement(name);

                SOAPElement userElement = header.addChildElement("username");
                userElement.addTextNode(username);
                SOAPElement passElement = header.addChildElement("password");
                passElement.addTextNode(password);
                msg.saveChanges();

                return true;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    @Override
    public boolean handleFault(SOAPMessageContext context) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void close(MessageContext context) {
        // TODO Auto-generated method stub
    }

    @Override
    public Set<QName> getHeaders() {
        // TODO Auto-generated method stub
        return null;
    }
}

至此,一个简单的基于CXF的webservice完成!





  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值