基于SSL验证的Apache CXF客户端设计总结

本文介绍如何使用Apache CXF框架配置SSL安全连接,并实现HTTPS方式调用WebService服务的过程。包括证书生成、服务器端配置、客户端调用等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在与合作方进行接口对接,合作方只提供了一个WSDL链接和一个ce.p12证书,研究了好几天终于调通了。为了与合作方对接,我自己在本地搭建了一个使用证书验证的Tomcat,在本地用自己制作的客户端证书与本地Tomcat进行SSL链接,最终终于搞清楚了使用Apache CXF框架进行SSL链接的方法。

整个过程可以分为以下几个:

  1. 认证过程
  2. 认识证书
  3. 制作证书
  4. 编写服务器端Web Service接口
  5. 配置Tomcat的SSL访问
  6. 编写客户端调用程序

1. 认证过程


    HTTPS认证其实是基于证书认证,一般常用的是x509认证,认证过程是这样的
  • 客户端 ==> 服务器端 : 客户端A需要使用自己的私钥进行签名,使用服务器端B的公钥进行加密,然后将请求数据传给服务器端B,服务器端B使用服务器端B的私钥进行解密,再用客户端A的公钥进行验签。
  • 服务器端 ==> 客户端 : 服务器端B使用服务器端B的私钥进行签名,使用客户端A的公钥进行加密,然后将响应数据传给客户端A,客户端A用客户端A的私钥进行解密,再用服务器端B的公钥进行验签。
    在JAVA中制作证书可以采用JDK中提供的keytool和openssl,两者的区别在于,如果证书中没有相关的证书链那么完全可以采用keytool,否则就需要用openssl工具。

2,认识证书

    我们要生成的证书包括以下几个:
  1. 根证书 : 根证书一般是起认证作用的,也可以称做CA,有相关的CA认证机构,不过一般需要花钱取实现CA认证,在这里根证书采用的是自签的方式。
  2. 服务器端证书 : 服务器端证书由根证书签署,在服务器端配置使用。
  3. 二级证书 : 二级证书由根证书签署,在服务器端配置使用。
  4. 客户端证书 : 客户端证书由根证书签署,在客户端配置使用。

3,制作证书


3.1 制作根证书

mkdir root
# 制作根证书
openssl genrsa -out root/root-key.pem 1024
# 创建证书请求
openssl req -new -out root/root-req.csr -key root/root-key.pem -subj /C=CN/ST=BeiJing/L=BeiJing/O="Xiaomi Technologies Co. Ltd."/OU="MIUI System Safety Team"/OU="MIUI System Safety Team"/CN="localhost"/emailAddress=xxxxxx@xiaomi.com
# 自签署根证书
openssl x509 -req -in root/root-req.csr -out root/root-cert.pem -signkey root/root-key.pem -days 3650
# 将证书导入到JKS文件中
/usr/lib/jvm/java/jdk1.6.0_45/bin/keytool -import -v -trustcacerts -storepass xiaomisys -alias root -file root/root-cert.pem -keystore root/root-id.jks
# 将证书导出成cer文件
/usr/lib/jvm/java/jdk1.6.0_45/bin/keytool -export -alias root -keystore root/root-id.jks -file root/root-id.cer -storepass xiaomisys

3.2 制作服务器端证书

cd root
#建立server文件夹
mkdir server
#回到root上级一目录
cd ..
#创建私钥
openssl genrsa -out root/server/temip-key.pem 1024
#创建证书请求(注意cn如果是本机应该填写localhost,如果是网站则填写域名.)
openssl req -new -out root/server/temip-req.csr -key root/server/temip-key.pem -subj /C=CN/ST=BeiJing/L=BeiJing/O="Xiaomi Technologies Co. Ltd."/OU="MIUI System Safety Team"/OU="MIUI System Safety Team"/CN=localhost/emailAddress=xxxxxx@xiaomi.com
#签署服务器端证书
openssl x509 -req -in root/server/temip-req.csr -out root/server/temip-cert.pem -CA root/root-cert.pem -CAkey root/root-key.pem -CAcreateserial -days 3650
#将服务器端证书PKCS12格式
openssl pkcs12 -export -clcerts -in root/server/temip-cert.pem -inkey root/server/temip-key.pem -out root/server/temip-id.p12

3.3 制作二级证书

cd root
#建立client文件夹
mkdir client
#回到root上一目录
cd ..
#创建私钥
openssl genrsa -out root/client/eomsca-key.pem 1024
#创建证书请求
openssl req -new -out root/client/eomsca-req.csr -key root/client/eomsca-key.pem -subj /C=CN/ST=BeiJing/L=BeiJing/O="Xiaomi Technologies Co. Ltd"/OU="MIUI System Safety Team"/OU="MIUI System Safety Team"/CN="localhost"/emailAddress=xxxxxx@xiaomi.com -reqexts v3_req
#自签署客户端证书
openssl x509 -req -in root/client/eomsca-req.csr -out root/client/eomsca-cert.pem -signkey root/client/eomsca-key.pem -CA root/root-cert.pem -CAkey root/root-key.pem -CAcreateserial -days 3650
#将客户端证书导出成浏览器可导入的PKCS12格式
openssl pkcs12 -export -clcerts -in root/client/eomsca-cert.pem -inkey root/client/eomsca-key.pem -out root/client/eomsca-id.p12

3.4 制作客户端证书

#创建私钥
openssl genrsa -out root/client/xm_xiaomi-key.pem 1024
#创建证书请求
openssl req -new -out root/client/xm_xiaomi-req.csr -key root/client/xm_xiaomi-key.pem -subj /C=CN/ST=BeiJing/L=BeiJing/O="Xiaomi Technologies Co. Ltd"/OU="MIUI System Safety Team"/OU="MIUI System Safety Team"/CN="localhost"/emailAddress=xxxxxx@xiaomi.com
#自签署客户端证书
openssl x509 -req -in root/client/xm_xiaomi-req.csr -out root/client/xm_xiaomi-cert.pem -signkey root/client/xm_xiaomi-key.pem -CA root/client/eomsca-cert.pem -CAkey root/client/eomsca-key.pem -CAcreateserial -days 3650
#将客户端证书导出成浏览器可导入的PKCS12格式
openssl pkcs12 -export -clcerts -in root/client/xm_xiaomi-cert.pem -inkey root/client/xm_xiaomi-key.pem -out root/client/xm_xiaomi-id.p12

3.5 最终的成果

最终要用到的证书文件有3个temip-id.p12,eomsca-id.p12,xm_xiaomi-id.p12,双击它们,输入密码,可以看到证书信息,比较重要的是CN(Common Name)在本地的话一定要填写成localhost,如果是对外的可以填写成IP地址:

证书

4,编写服务器端Web Service接口

4.1 项目目录

搭建Apache CXF框架和环境的方法就忽略了,网上很多了,下面是我的服务器端demo代码结构:

cxf-demo

依赖的jar包

4.2 Web Service接口代码

package com.server;

import javax.jws.WebService;

/**
 * Web Service 接口声明
 * @author liang
 */
@WebService(targetNamespace = "server.com")
public interface HelloWorld {

	/**
	 * sayHi
	 * @param text
	 * @return
	 */
	String sayHi(String text);
}

4.3 Web Service实现类代码

package com.server;

import javax.jws.WebService;

/**
 * Web Service接口实现
 * @author liang
 *
 */
@WebService(endpointInterface = "com.server.HelloWorld")
public class HelloWorldImpl implements HelloWorld {

	@Override
	public String sayHi(String text) {
		// TODO Auto-generated method stub
		return "Hello, " + text;
	}

}

4.4 spring-cxf.xml内容

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:jaxws="http://cxf.apache.org/jaxws"
	xmlns:cxf="http://cxf.apache.org/core"
	xmlns:wsa="http://cxf.apache.org/ws/addressing"
	xsi:schemaLocation="
	  http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.1.xsd
	 http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
	http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
	<import resource="classpath:META-INF/cxf/cxf.xml" />
	<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />

	 <cxf:bus>
        <cxf:features>
        	<!--日志拦截功能,用于监控soap内容,开发后可以删除 -->
            <cxf:logging/>
            <wsa:addressing/>
        </cxf:features>
    </cxf:bus>
	<bean id="hello" class="com.server.HelloWorldImpl" />

	<jaxws:endpoint id="helloWorld" implementor="#hello" address="/HelloWorld" publish="true"/>

</beans>

4.5 web.xml内容

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name></display-name>
  <!-- spring需要加载的配置文件 -->
  <context-param>
  	<param-name>contextConfigLocation</param-name>
  	<param-value>
  		classpath:com/server/spring-cxf.xml
  	</param-value>
  </context-param>	
  <listener>
  		<listener-class>
  			org.springframework.web.context.ContextLoaderListener
  		</listener-class>
  </listener>
  <!-- cxf服务启动servlet -->
  <servlet>
  		<servlet-name>CXFServlet</servlet-name>
  		<servlet-class>
  			org.apache.cxf.transport.servlet.CXFServlet
  		</servlet-class>
  		<load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
  		<servlet-name>CXFServlet</servlet-name>
  		<url-pattern>/service/*</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

4.5 服务器端总结

此时,我们还没有配置Tomcat的SSL访问设置,因此可以使用http来访问WSDL,启动Tomcat后可以在浏览器中输入http://localhost:8080/cxf-deom/service/HelloWorld?WSDL就可以看到Web Service的描述了。

5,Tomcat配置SSL

现在,我们来配置服务器端的Tomcat,使得服务器端Tomcat支持SSL访问

5.1,配置Tomcat

找到Tomcat的conf目录下的server.xml,搜索"8443"找到对应SSL的配置,该配置段默认是注释掉的,解除注释并修改成下面的样子:

<Connector port="8443"  address="0.0.0.0" protocol="HTTP/1.1" SSLEnabled="true" 
               maxThreads="150" scheme="https" secure="true" 
               clientAuth="true" sslProtocol="TLS"  
	       keystoreFile="/home/work/server/cer/temip-id.p12" keystoreType="PKCS12" keystorePass="xiaomisys" 
	       truststoreFile="/home/work/server/cer/eomsca-id.p12" truststoreType="PKCS12" truststorePass="xiaomisys"/> 

修改完Tomcat配置后,重新启动Tomcat,在浏览器中输入https://localhost:8443/cxf-deom/service/HelloWorld?WSDL,会发现无法访问了。

5.2,chrome浏览器中导入证书

打开chrome的设置页面,搜索SSL,然后点击"Mange certificates",点击"import",然后导入客户端证书xm-xiaomi-id.p12,导入时会要求输入密码。
再次访问https://localhost:8443/cxf-deom/service/HelloWorld?WSDL,chrome会弹出对话框,要求确认证书,点击"OK",接下来会提醒你说"该证书是不被信任的",选择"Process anyway",然后就能看到WSDL页面了。

import



trust

wsdl



如果最终看到了WSDL就说名Tomcat配置是正确的,证书也是正确的。


6,编写客户端程序


我们客户端要想通过HTTPS访问服务器端的Web Service服务,首先是要在客户端生成一个服务器Web Service服务的对象实例,其次是为给对象实例设置证书信息,然后就可以用这个对象实例直接调用Web Service声明的方法来访问了。

6.1 客户端设置信任证书信息 -- 使用代码实现

package com.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.transport.http.HTTPConduit;

import com.server.HelloWorld;

public class ClientUtils {

	private static HelloWorld helloWorld;

	public static HelloWorld getInstance(){

		if(null != helloWorld){
			return helloWorld;
		}

		try{
			String addr = "https://localhost:8443/cxf-demo/service/HelloWorld";
			JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
			factoryBean.setAddress(addr);
			factoryBean.setServiceClass(HelloWorld.class);
			helloWorld = (HelloWorld) factoryBean.create();
			  Client proxy = ClientProxy.getClient(helloWorld);
			  HTTPConduit conduit = (HTTPConduit) proxy.getConduit();
			  TLSClientParameters tlsParams = conduit.getTlsClientParameters();
			  if (tlsParams == null) {
			   tlsParams = new TLSClientParameters();
			  }
			  tlsParams.setDisableCNCheck(true);
			  //设置keystore
			  tlsParams.setKeyManagers(ClientUtils.getKeyManagers());
			  // 设置信任证书
			  tlsParams.setTrustManagers(ClientUtils.getTrustManagers());
			  conduit.setTlsClientParameters(tlsParams);
		}catch(Exception e){
			e.printStackTrace();
		}

		return helloWorld;
	}

	public static KeyManager[] getKeyManagers() {
		  InputStream is = null;
		  try {
		   // 获取默认的 X509算法
		   String alg = KeyManagerFactory.getDefaultAlgorithm();
		   // 创建密钥管理工厂
		   KeyManagerFactory factory = KeyManagerFactory.getInstance(alg);
		   File certFile = new File("/home/liang/Documents/works/servers/tomcat/cer/xm_xiaomi-id.p12");
		   if (!certFile.exists() || !certFile.isFile()) {
		    return null;
		   }
		   is = new FileInputStream(certFile);
		   // 构建以证书相应格式的证书仓库
		   KeyStore ks = KeyStore.getInstance("pkcs12");
		   // 加载证书
		   ks.load(is, "changeit".toCharArray());
		   factory.init(ks, "changeit".toCharArray());
		   KeyManager[] keyms = factory.getKeyManagers();
		   return keyms;
		  } catch (Exception e) {
		   e.printStackTrace();
		  } finally {
		   if (is != null) {
		    try {
		     is.close();
		    } catch (IOException e) {
		     e.printStackTrace();
		    }
		   }

		  }
		  return null;
		}

	public static TrustManager[] getTrustManagers() {
		  // 读取证书仓库输入流
		  InputStream is = null;
		  try {
		   // 信任仓库的默认算法X509
		   String alg = TrustManagerFactory.getDefaultAlgorithm();
		   // 获取信任仓库工厂
		   TrustManagerFactory factory = TrustManagerFactory.getInstance(alg);
		   // 读取信任仓库
		   is = new FileInputStream(new File("/home/liang/Documents/works/servers/tomcat/cer/temip-id.p12"));
		   // 密钥类型
		   KeyStore ks = KeyStore.getInstance("pkcs12");
		   // 加载密钥
		   ks.load(is, "changeit".toCharArray());
		   factory.init(ks);
		   TrustManager[] tms = factory.getTrustManagers();
		   return tms;
		  } catch (Exception e) {
		   e.printStackTrace();

		  } finally {
		   if (is != null) {
		    try {
		     is.close();
		    } catch (IOException e) {
		     e.printStackTrace();
		    }
		   }
		  }
		  return null;
		}
}

6.2 客户端置信任证书信息 -- 使用配置文件实现


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:soap="http://cxf.apache.org/bindings/soap" xmlns:context="http://www.springframework.org/schema/context"
xmlns:sec="http://cxf.apache.org/configuration/security" xmlns:http="http://cxf.apache.org/transports/http/configuration"
xsi:schemaLocation="http://www.springframework.org/schema/beans
					http://www.springframework.org/schema/beans/spring-beans.xsd
					http://cxf.apache.org/bindings/soap
					http://cxf.apache.org/schemas/configuration/soap.xsd
					http://cxf.apache.org/jaxws
					http://cxf.apache.org/schemas/jaxws.xsd
					http://cxf.apache.org/configuration/security
					http://cxf.apache.org/schemas/configuration/security.xsd
					http://cxf.apache.org/transports/http/configuration
					http://cxf.apache.org/schemas/configuration/http-conf.xsd">

	<jaxws:client id="helloClient"
                  serviceClass="com.server.HelloWorld"
                  address="https://localhost:8443/cxf-demo/service/HelloWorld" />

	    <!-- *代码所的客户端可以访问 -->
	<http:conduit name="*.http-conduit">
	  <http:tlsClientParameters disableCNCheck="true">
	   <sec:trustManagers>
	    <sec:keyStore type="PKCS12" password="changeit" file="/home/liang/Documents/works/servers/tomcat/cer/temip-id.p12" />
	   </sec:trustManagers>
	   <!--双向认证 -->
	   <sec:keyManagers keyPassword="changeit">
	    <sec:keyStore type="PKCS12" password="changeit" file="/home/liang/Documents/works/servers/tomcat/cer/xm_xiaomi-id.p12" />
	   </sec:keyManagers>

	   <sec:cipherSuitesFilter>
	    <!-- these filters ensure that a ciphersuite with export-suitable or
	     null encryption is used, but exclude anonymous Diffie-Hellman key change
	     as this is vulnerable to man-in-the-middle attacks -->
	    <sec:include>.*_EXPORT_.*</sec:include>
	    <sec:include>.*_EXPORT1024_.*</sec:include>
	    <sec:include>.*_WITH_DES_.*</sec:include>
	    <sec:include>.*_WITH_NULL_.*</sec:include>
	    <sec:exclude>.*_DH_anon_.*</sec:exclude>
	   </sec:cipherSuitesFilter>
	  </http:tlsClientParameters>
	</http:conduit>
</beans>

6.3 客户端通过HTTPS调用服务器Web Service服务


package com.client;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.server.HelloWorld;
import com.util.ClientUtils;

/**
 * 客户端访问服务器Web Service
 * @author liang
 *
 */
public final class ClientTest {

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

		test2();

	}

	/**
	 * 测试1,通过代码设置实例证书
	 */
	public static void test1(){
		HelloWorld client = ClientUtils.getInstance();

		String response = client.sayHi("Joe");
		System.out.println("Response: " + response);
		System.exit(0);
	}

	/**
	 * 测试2, 通过配置文件设置实例证书
	 */
	public static void test2(){
			ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
					"beans.xml");

			HelloWorld client = (HelloWorld) context.getBean("helloClient");

			String response = client.sayHi("Joe");
			System.out.println("Response: " + response);
			System.exit(0);
	}

}

以上两种方法都可以访问SSL设置的Web Service服务,然后在控制台打印输出信息。

至此,一个通过HTTPS访问Web Service的例子就搞定了。

参考 : http://liuwuhen.iteye.com/blog/1661493

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值