您的位置:首页 > 运维架构 > Apache

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

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

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

认证过程
认识证书

制作证书
编写服务器端Web Service接口
配置Tomcat的SSL访问
编写客户端调用程序

1. 认证过程

HTTPS认证其实是基于证书认证,一般常用的是x509认证,认证过程是这样的

客户端 ==> 服务器端 : 客户端A需要使用自己的私钥进行签名,使用服务器端B的公钥进行加密,然后将请求数据传给服务器端B,服务器端B使用服务器端B的私钥进行解密,再用客户端A的公钥进行验签。
服务器端 ==> 客户端 : 服务器端B使用服务器端B的私钥进行签名,使用客户端A的公钥进行加密,然后将响应数据传给客户端A,客户端A用客户端A的私钥进行解密,再用服务器端B的公钥进行验签。

在JAVA中制作证书可以采用JDK中提供的keytool和openssl,两者的区别在于,如果证书中没有相关的证书链那么完全可以采用keytool,否则就需要用openssl工具。

2,认识证书

我们要生成的证书包括以下几个:

根证书 : 根证书一般是起认证作用的,也可以称做CA,有相关的CA认证机构,不过一般需要花钱取实现CA认证,在这里根证书采用的是自签的方式。
服务器端证书 : 服务器端证书由根证书签署,在服务器端配置使用。
二级证书 : 二级证书由根证书签署,在服务器端配置使用。
客户端证书 : 客户端证书由根证书签署,在客户端配置使用。

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代码结构:





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页面了。









如果最终看到了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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: