您的位置:首页 > 编程语言 > Java开发

SpringBoot学习笔记之CXF集成(实现用户验证)

2017-09-22 09:33 726 查看
Springboot集成CXF

说起web service最近几年restful大行其道,大有取代传统soap web service的趋势,但是一些特有或相对老旧的系统依然使用了传统的soap web service,例如银行、航空公司的机票查询接口等。目前就遇到了这种情况,需要在系统中查询第三方提供的soap web service接口,也就是说要将它整合进现有的系统当中。Spring整合CXF本来十分简单,但是因为使用了Spring
boot,不想用以前xml一堆配置的方式,那么能否按照Spring boot的风格优雅的进行整合呢?答案当然是肯定的,但是遍查网上几乎没有这方面的资料,折腾过后觉得还是有必要记录一下,虽然它显得非常的简单。

1)、引入cxf 依赖包

<!-- CXF webservice --><dependency><groupId>org.apache.cxf</groupId><artifactId>cxf-spring-boot-starter-jaxws</artifactId><version>3.1.7</version></dependency><!-- CXF webservice -->

2、开发webservice接口类

import javax.jws.WebMethod;

import javax.jws.WebParam;

import javax.jws.WebResult;

import javax.jws.WebService;

@WebService(name="ITestWebService",//暴露服务名称

targetNamespace="http://webservice.liyj.vk.com")//命名空间,一般是接口的包名倒序

public interface ITestWebService {

@WebMethod

@WebResult(name="String",targetNamespace="")

public String sayHello(@WebParam String name);

}

3、开发接口实现类

import javax.jws.WebService;

import org.springframework.stereotype.Component;

@WebService(serviceName="ITestWebService",

targetNamespace="http://webservice.liyj.vk.com",

endpointInterface="com.vk.liyj.webservice.ITestWebService")

@Component

public class TestWebSericeImpl implements ITestWebService {

@Override

public String sayHello(String name) {

return "hello "+name;

}

}

4、定义拦截器用于用户验证

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中对消息进行特殊处理,实现了很多重要功能模块。这里就是采用拦截器进行用户验证。

5、客户端登录拦截器 ClientLoginInterceptor

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));

}

}

6、开发cxf配置类发布服务

import javax.xml.ws.Endpoint;

import org.apache.cxf.Bus;

import org.apache.cxf.jaxws.EndpointImpl;

import org.apache.cxf.transport.servlet.CXFServlet;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.web.servlet.ServletRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class CxfConfig {

@Autowired

private Bus bus;

@Autowired

private ITestWebService service;

//必须要有

@Bean

public ServletRegistrationBean cxfServlet(){

return new ServletRegistrationBean(new CXFServlet(),"/services/*");

}

@Bean

public Endpoint endpoint(){

EndpointImpl endpoint=new EndpointImpl(bus,service);

endpoint.publish("/testWebService");

//打印报文日志拦截器

endpoint.getInInterceptors().add(new LoggingInInterceptor());

endpoint.getInInterceptors().add(new LoggingOutInterceptor());

//通过拦截器校验用户名与密码

endpoint.getInInterceptors().add(new AuthInterceptor());

return endpoint;

}

}

说明:

i、上类中 cxfServlet()方法相当于传统web.xml中的下列代码

<servlet>

<servlet-name>CXFServlet</servlet-name>

<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>CXFServlet</servlet-name>

<url-pattern>/server/*</url-pattern>

</servlet-mapping>

ii、上类中的endpoint()方法相当于传统xml配置文件中的下列代码,LoggingInInterceptor,LoggingOutInterceptor用于打印webservice调用日志。

<jaxws:endpoint id="meetingService"

implementor="#meetingWsEndpoint" address="/meetingService" >

<jaxws:inInterceptors>

<bean class="org.apache.cxf.interceptor.LoggingInInterceptor" />

</jaxws:inInterceptors>

<jaxws:outInterceptors>

<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor" />

</jaxws:outInterceptors>

</jaxws:endpoint>

通过对比我们可以看到Spring boot和cxf的整合比以前xml的方式更加的简洁。

启动应用访问 http://localhost:8080/web/services
可查看所有相应用下发布的服务,访问http://localhost:8080/web/services/testWebService?wsdl
可查看wsdl.

webservice服务已经发布了,那么我们怎么调用已经发布的服务呢?有两种比较通过的调用方法,非使用wsdl文档生成java类。本人喜欢传入方法名调用的方式,显得清爽而简洁。两种调用服务的代码如下:

import org.apache.cxf.endpoint.Client;

import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;

import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;

public class TestWebservice {

public static void main(String[] args) throws Exception {

cl2();

}

/**

* 方式1.代理类工厂的方式,需要拿到对方的接口

*/

public static void cl1() {

try {

// 接口地址

String address = "http://localhost:8080/services/CommonService?wsdl";

// 代理工厂

JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();

// 设置代理地址

jaxWsProxyFactoryBean.setAddress(address);

// 设置接口类型

jaxWsProxyFactoryBean.setServiceClass(ITestWebService.class);

// 创建一个代理接口实现

ITestWebService cs = (ITestWebService) jaxWsProxyFactoryBean.create();

// 数据准备

String userName = "liyj";

// 调用代理接口的方法调用并返回结果

String result = cs.sayHello(userName);

System.out.println("返回结果:" + result);

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 动态调用方式

*/

public static void cl2() {

// 创建动态客户端

JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();

Client client = dcf.createClient("http://localhost:8080/web/services/testWebService?wsdl");

// 需要密码的情况需要加上用户名和密码

// client.getOutInterceptors().add(new ClientLoginInterceptor(USER_NAME,

// PASS_WORD));

Object[] objects = new Object[0];

try {

// invoke("方法名",参数1,参数2,参数3....);

objects = client.invoke("sayHello", "liyj");

System.out.println("返回数据:" + objects[0]);

} catch (java.lang.Exception e) {

e.printStackTrace();

}

}

}

使用动态调用方式要注意的就是,如果调用的服务接口返回的是一个自定义对象,那么结果Object[]中的数据类型就成了这个自定义对象(组件帮你自动生成了这个对象),但是你本地可能并没有这个类,所以需要自行转换处理,最简单的是新建一个跟返回结果一模一样的类进行强转,当然更好的方式是封装一个通用的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息