1. 程式人生 > >SpringBoot學習筆記之CXF整合(實現使用者驗證)

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的方式更加的簡潔。 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[]中的資料型別就成了這個自定義物件(元件幫你自動生成了這個物件),但是你本地可能並沒有這個類,所以需要自行轉換處理,最簡單的是新建一個跟返回結果一模一樣的類進行強轉,當然更好的方式是封裝一個通用的。