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

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[] 中的数据类型就成了这个自定义对象(组件帮你自动生成了这个对象),但是你本地可能并没有这个类,所以需要自行转换处理,最简单的是新建一个跟返回结果一模一样的类进行强转,当然更好的方式是封装一个通用的。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值