【WebService】【CXF】【多接口】【身份认证】【Java】

需求:实现基于cxf框架的webservice

主要内容

  1. 实现基于cxf框架的webservice
  2. webservice发布多个服务
  3. 实现webservice的安全访问

开发环境

  • jdk 1.8
  • eclipse 2018-10-09版
  • maven 3.5
  • tomcat 8.5

调试工具

  • 浏览器
  • SoapUI接口调试工具(推荐**)

 

spring项目实现基于cxf框架的webservice

1、cxf jar包

		<!-- cxf相关依赖 -->
		<!-- cxf-core -->
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-core</artifactId>
			<version>3.2.5</version>
		</dependency>
		<!-- cxf-rt-frontend-jaxws -->
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-frontend-jaxws</artifactId>
			<version>3.2.5</version>
		</dependency>
		<!-- cxf-rt-transports-http -->
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-transports-http</artifactId>
			<version>3.2.5</version>
		</dependency>
		<!-- cxf-rt-ws-security -->
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-ws-security</artifactId>
			<version>3.2.5</version>
		</dependency>

2、spring.xml相关配置

约束命名空间不能少

	<!-- 下面是使用 cxf 配置的 webService -->
	<!-- define web service provider -->
	<!-- 配置第一个服务 -->
	<!-- 要暴露给外部调用的接口,address:请求路径 -->
	<jaxws:endpoint id="jdWebService"
		implementor="com.zy.api.publish.route.jdWebServiceImpl"
		address="/jdWebService">
		<!-- 添加客服端请求拦截器 -->
		<jaxws:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
		</jaxws:inInterceptors>
		<!-- 添加服务器端响应拦截器 -->
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
		</jaxws:outInterceptors>
	</jaxws:endpoint>

3、web.xml配置

	<!-- webservice之cxf相关配置 -->
	<servlet>
		<servlet-name>CXF</servlet-name>
		<servlet-class>org.apache.cxf.transport.servlet.CXFServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>CXF</servlet-name>
		<url-pattern>/services/*</url-pattern>
	</servlet-mapping>

4、接口类编写:

package com.zy.api.publish.route;
import javax.jws.WebParam;
import javax.jws.WebService;
/**
 * @author  TSY
 *
 * @Date:  2018-09-10
 *
 *
 */
@WebService(targetNamespace = "com.serviceTargetName")
public interface jdWebService {
	 
	 public String waterPredictMinute(@WebParam(name = "jsonObj") String json);
	 

	 public String waterPredictHour(@WebParam(name = "jsonObj") String json);
	 
	 
	 public String warning(@WebParam(name = "jsonObj") String json);
	 
	 
	 public String dispatcher(@WebParam(name = "jsonObj") String json);
	 

	 public String closeLoopDispatcher(@WebParam(name = "jsonObj") String json);
	 
	 
	 

	 public String closeLoopWater(@WebParam(name = "jsonObj") String json);
	 
}


///////////////////  webservice开发说明   //////////////////////
/*
 * @ 1.这里的@Webservice(targetNamespace="")的作用类似与spring的Controller层中的Controller("/helloworld"),用于定位
 * @ 2.在要发布的服务接口类开头加上@WebService 在接口的实现类开头也加上@WebService 若两个类不在同一个包中
 *  则还要在实现类上用targetNamespace指明目标命名空间。命名空间的值和接口上的值一样。
 *         
 */

5、实现类编写

import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.jws.WebService; 
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zy.core.enums.TypeEnum;
import com.zy.core.utils.DateUtil;
import com.zy.core.utils.FastJsonUtil;
import com.zy.core.utils.JacksonUtil;
import com.zy.system.entity.DispatcherScheme;
import com.zy.system.entity.PumpInfo;
import com.zy.system.entity.TempReviceData;
import com.zy.system.entity.WorkFlow;
import com.zy.system.service.DispatcherSchemeService;
import com.zy.system.service.PumpInfoService;
import com.zy.system.service.TempReciveDataService;
import com.zy.system.service.WorkFlowService;
import com.zy.system.websocket.CloseLoopDispatcherWebSocket;
import com.zy.system.websocket.JinhaiWebSocket;
import com.zy.system.websocket.WHGWebSocket;
import com.zy.system.websocket.WaterDemandWebSocket;
import com.zy.system.websocket.WorkFlowWebSocket;
/**
 * @author  TSY
 *
 * @Date:  2018-09-11
 *
 */
/*
 * 	由于实现类和接口不在同一个包中。所以要加上targetNamespace属性。
 * 	另外,这里的endpointInterface是实现类对应接口的全路径
 * 
 */
@WebService(targetNamespace="com.serviceTargetName",endpointInterface="com.zy.api.publish.route.jdWebService")
@Component("jdWebService")//spring注入用
public class jdWebServiceImpl implements jdWebService{
	@Autowired
	private XxxService xxxService;

	//ip地址+端口+工程名+webxml配置路径+spring配置的路径+?wsdl
	//url=localhost:8080/xxxx/services/jdWebService?wsdl
	

	@Override
	public String closeLoopDispatcher(String json) {
		//业务处理
		return "success";
	}

	@Override
	public String closeLoopWater(String json) {
		//业务处理
		return "success";
	}
	
	@Override
	public String waterPredictMinute(String json) {
		return "success";
	}

	@Override
	public String waterPredictHour(String json) {
		return "success";
	}

	@Override
	public String warning(String json) {
		return "success";
	}

	@Override
	public String dispatcher(String json) {
		return "success";
	}

}

/* 以下是webService 的注解说明
 * @param endpointInterface指定接入点接口:接口必须存在 
 * @param targetNamespace service 命名空间,一般为域名倒写
 * @param serviceName: 对外发布的服务名,指定 Web Service 的服务名
 * 称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串)
 */

6、验证接口是否发布成功

请求地址:   

    //ip地址+端口+工程名+webxml配置路径+spring配置的路径+?wsdl
    //url=localhost:8080/xxx/services/jdWebService?wsdl

在浏览器中输入访问地址,出现wsdl文档说明,发布成功啦

7、客户端调用

  1. 最原始的http请求
  2. 通过生成客户端代码工具自动生成请求代码生成Webservice客户端的4种方法
  3. 纯java 调用,不使用任何框架https://blog.csdn.net/chrisjingu/article/details/52385632

  4. 通过框架调用(略)

http请求的调用方法如下:

  • Dom4j解析返回的xml文件
  • 需要拼装请求的soapxml
package com.zy.api.recieve.action;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**     
 * @author  TSY
 *
 * @Date:  2018-09-25
 *
 */
public class GiveWaterDemandService {
	public static final String address = "http://xxxxx.nat123.cc:18837/?wsdl";//外网测试用
	//public static final String address = "http://192.168.137.25:8000/?wsdl";//内网正式用
	public static String giveWaterDemandToJD(String waterdemand)  {
		String result=null;
		InputStream is =null;
		try {
		 //第一步:创建服务地址  
        URL url = new URL(address);  
        //第二步:打开一个通向服务地址的连接  
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();  
        //第三步:设置参数  
        //3.1发送方式设置:POST必须大写  
        connection.setRequestMethod("POST");  
        //3.2设置数据格式:content-type  
        connection.setRequestProperty("content-type", "text/xml;charset=utf-8");  
        //3.3设置输入输出,因为默认新创建的connection没有读写权限,  
        connection.setDoInput(true);  
        connection.setDoOutput(true);  
  
        //第四步:组织SOAP数据,发送请求  
        String soapXML = getXML(waterdemand);  
        //将信息以流的方式发送出去
        OutputStream os = connection.getOutputStream();  
        os.write(soapXML.getBytes());  
        //第五步:接收服务端响应,打印  
        int responseCode = connection.getResponseCode();  
        if(200 == responseCode){//表示服务端响应成功  
            //获取当前连接请求返回的数据流
            is = connection.getInputStream();  
            /*
             * dom4j解析返回的流信息
             */
            result = ParseSoapXML.result(is);
            System.out.println("推送确认的需水量数据给交大结果++++++++++++++++++++++++"+result);  
        }  
        os.close();  
        
		}catch( IOException e) {
			System.out.println("异常--推送确认的需水量数据给交大结果++++++++++++++++++++++++"+ e.getMessage());
		}finally {
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}  
		}
		return result;
	}
	
	 public static String getXML(String waterdemand){  
		 String soapXML ="<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tns=\"tns\">" + 
		 		"<soapenv:Header/>" + 
		 		"<soapenv:Body>" + 
		 		"<tns:waterdemand>" + 
		 		"<tns:data>"+ 
		 			waterdemand
		 		+ "</tns:data>" + 
		 		"</tns:waterdemand>" + 
		 		"</soapenv:Body>" + 
		 		"</soapenv:Envelope>"
				 ;
	        return soapXML;  
	    }  


}
package com.zy.api.recieve.action;
import java.io.InputStream;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 
 * @author Administrator
 * 
 *	dom4j解析需水量webservice返回的流信息
 */
public class ParseSoapXML {
	public static String result(InputStream xml){
		try {
		SAXReader reader = new SAXReader();
		Document doucment = reader.read(xml);
		Element root = doucment.getRootElement();		
		Element bo = root.element("Body");
		Element response = bo.element("waterdemandResponse");
		Element re = response.element("waterdemandResult");
		System.out.println(re.getText());
		return re.getText();
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return "false"; 
	}
}

webservice发布多个服务

很简单,将整个cxf相关的xml文件块,复制一份即可

然后,编写相关的接口和实现类

	<!-- 下面是使用 cxf 配置的 webService -->
	<!-- define web service provider -->
	<!-- 配置第一个服务 -->
	<!-- 要暴露给外部调用的接口,address:请求路径 -->
	<jaxws:endpoint id="jdWebService"
		implementor="com.zy.api.publish.route.jdWebServiceImpl"
		address="/jdWebService">
		<!-- 添加客服端请求拦截器 -->
		<jaxws:inInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
		</jaxws:inInterceptors>
		<!-- 添加服务器端响应拦截器 -->
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
		</jaxws:outInterceptors>
	</jaxws:endpoint>

	<!-- 配置第二个服务 -->
	<jaxws:endpoint id="tempWebService"
		implementor="com.zy.api.publish.route.egbWebServiceImpl"
		address="/egb/thirdParty/toData">
		<jaxws:inInterceptors> <!-- WS-Security拦截器 -->
			<bean
				class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
				<constructor-arg>
					<map>
						<entry key="action" value="UsernameToken" />
						<!-- 密码类型,PasswordText表示明文,密文是PasswordDigest -->
						<entry key="passwordType" value="PasswordText" />
						<entry key="passwordCallbackRef"> <!-- 回调函数引用 -->
							<ref bean="myPasswordCallback" />
						</entry>
					</map>
				</constructor-arg>
			</bean>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
		</jaxws:outInterceptors>
	</jaxws:endpoint>

实现webservice的安全访问

1、导入security相关的jar包

2、配置xml文件

     WS-Security拦截器,回掉函数需要注入到bean(<bean class="com.zy.api.publish.route.ServerPasswordCallback"
        id="myPasswordCallback" />)、回调函数主要用来对密码和账户进行确认的,一般可以从数据库进行查询,

<jaxws:endpoint id="tempWebService"
		implementor="com.zy.api.publish.route.egbWebServiceImpl"
		address="/egb/thirdParty/toData">
		<jaxws:inInterceptors> <!-- WS-Security拦截器 -->
			<bean
				class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
				<constructor-arg>
					<map>
						<entry key="action" value="UsernameToken" />
						<!-- 密码类型,PasswordText表示明文,密文是PasswordDigest -->
						<entry key="passwordType" value="PasswordText" />
						<entry key="passwordCallbackRef"> <!-- 回调函数引用 -->
							<ref bean="myPasswordCallback" />
						</entry>
					</map>
				</constructor-arg>
			</bean>
			<bean class="org.apache.cxf.interceptor.LoggingInInterceptor"></bean>
		</jaxws:inInterceptors>
		<jaxws:outInterceptors>
			<bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"></bean>
		</jaxws:outInterceptors>
	</jaxws:endpoint>
<bean
		class="com.zy.api.publish.route.ServerPasswordCallback"
		id="myPasswordCallback" />

2、编写回调函数

package com.zy.api.publish.route;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.wss4j.common.ext.WSPasswordCallback;
/*
 * 参考博客:https://blog.csdn.net/chrisjingu/article/details/52385632
 */
public class ServerPasswordCallback implements CallbackHandler {

	@Override
	public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
		WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
		if ("ThirdParty".equals(pc.getIdentifier())) {
			pc.setPassword("egbIh87wmnHbFiKqad9M8NNtdql");
		}
	}
}




/*
 * 使用UsernameToken方式验证,在server端的回调函数中WSPasswordCallback只能获得用户名(identifier),一般这里的逻辑是使用这个用户名到数据库中查询其密码,
 * 然后再设置到password属性,WSS4J 会自动比较客户端传来的值和你设置的这个值。你可能会问为什么这里CXF 不把客户端提交的密码传入让我们在ServerPasswordCallbackHandler中比较呢?
 * 这是因为客户端提交过来的密码在SOAP 消息中已经被加密为MD5 的字符串,如果我们要在回调方法中作比较,那么第一步要做的就是把服务端准备好的密码加密为MD5 字符串,
 * 由于MD5 算法参数不同结果也会有差别,另外,这样的工作CXF 替我们完成了.

 */
/*
 * 使用ws-security进行安全验证,概括来说分两步:
一、创建一个验证类,如本实例的ServerPasswordCallback.class
二、配置WSS4JInInterceptor拦截器,如applicationContext-cxf.xml所示。
三、将applicationContext-cxf.xml配置到web.xml的contextConfigLocation属性中 

 *该类实现ws-security的CallbackHandler 接口,并重写它的handle方法。该类就是身份验证的主要类,
 *当客户端传过的用户名中为“huwei“时,该方法会将指定的密码告知ws-security的WSPasswordCallback 类,
 *并让它后期去和客户端的密码进行验证,通过就放行,否则打回。
 *在applicationContext-cxf.xml中该类会作为WSS4JInInterceptor拦截器的回调函数属性,进行配置。

 */

3、客户端测试(SoapUI)

【浏览器、soapui都可以进行查看,但是无法进行正确调用,需要设置用户名和密码】

提示如下错误:

需要设置用户名和密码;

比如:soapui直接再header里面加上如下信息,即可

用其他框架也可以设置用户名和密码进行请求

小技巧:http请求时,xml文件可以直接copy 工具SoapUI生成的xml文件

      <soapenv:Header>
   <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> 
   <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
   <wsse:Username>ThirdParty</wsse:Username>
   <wsse:Password Type= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">
   egbIh87wmnHbFiKqad9M8NNtdql</wsse:Password> 
   </wsse:UsernameToken> </wsse:Security>
    <soapenv:Header/>

猜你喜欢

转载自blog.csdn.net/myblogzz/article/details/83578383