cxf开发实践(添加SSL支持)

原文 http://www.newsmth.net/pc/pccon.php?id=10002535&nid=340064


1 SSL概述

安全套接层(SSL)及其新继任者传输层安全(TLS)是在互联网上提供保密安全通道的加密协议,为诸如网站、电子邮件、网上传真等等数据传输进行保密。TLS利用密钥算法在互联网上提供端点身份认证与通讯保密。在典型例子中,只有服务器被可靠身份认证(即其验证被确保),客户端踪迹不一定经可靠认证;相互间的身份认证需要公钥基础设施(PKI)设置于客户端中。协议的设计在某种程度上能够使客户端/服务器应用程序通讯本身预防窃听、干扰(Tampering)、和消息伪造。

TLS包含三个基本阶段:

1.       对等协商密钥算法支持

2.       基于公钥密码的密钥交换和基于证书的身份认证

3.       基于对称密钥的数据传输保密

在第一阶段,客户端与服务器协商所用密码算法。 当前广泛实现的算法选择如下:

l        公钥密码系统:RSA、Diffie-Hellman、DSA及Fortezza;

l        对称密钥系统:RC2、RC4、IDEA、DES、Triple DES及AES;

l        单向散列函数:MD5及SHA。

TLS的记录层(Record layer)用于封装更高层的HTTP等协议。记录层数据可以被随意压缩、加密,与消息验证码(MAC)打包在一起。每个记录层包都有一个content_type段用以记录更上层用的协议。

当一个连接被发起时,从客户端的角度看,要收发几个握手信号:

1.       发送一个ClientHello消息,说明它支持的密码算法列表、压缩方法及最高协议版本,也发送稍后将被使用的随机字节。

2.       然后收到一个ServerHello消息,包含服务器选择的连接参数,源自客户端初期所提供的ClientHello。

3.       当双方知道了连接参数,客户端与服务器交换证书(依靠被选择的公钥系统)。这些证书通常基于X.509,不过已有草案支持以OpenPGP为基础的证书。

4.       服务器能够请求得到来自客户端的证书,所以连接可以是相互的身份认证。

5.       客户端与服务器通过加密通道协商一个共同的“主密钥”,这通过精心谨慎设计的伪随机数函数实现。结果可能使用Diffie-Hellman交换,或简单的公钥加密,双方各自用私钥解密。所有其他关键数据的加密均使用这个“主密钥”。

TLS/SSL有多样的安全保护措施。所有的记录层数据均被编号,序号用在消息验证码(MAC)中。

2 公开密钥加密、证书与CA的概念

公开密钥加密也称为非对称密钥加密,该加密算法使用两个不同的密钥:公开密钥和私有密钥。这两个密钥是数学相关的,用某用户私钥加密后所得的信息只能用该用户的公钥才能解密,反之亦然。RSA算法(由发明者Rivest,Shmir和Adleman姓氏首字母缩写而来)是最著名的公开密钥加密算法。非对称密钥加密的另一用途是身份验证:用私钥加密的信息,只能用公钥对其解密,接收者由此可知这条信息确实来自于拥有私钥的某人。

顾名思义,公开密钥是处于公共域的,它通常被发布出去。公钥发布的形式就是证书,其中除了包含公钥之外,还包含了身份信息,以及CA的签名。证书的传输格式标准一般使用X509证书格式。反之,私钥是被保护存储的,可以使用硬件或者普通的文件来存储私钥。一般使用PKCS#12格式将它和公钥加密存储在普通的文件中。在Java平台,一般也使用JKS格式。

因为证书中包含了身份信息,因为在商务应用中,交易双方会对证书进行验证。方法是查看CA签名。CA是指身份认证机构,是被交易双方都信任的第三方实体。任何人可以将自己的公钥和身份信息提交给CA进行认证,由CA对身份信息进行确实,然后签名发证。CA实际上也是使用非对称加密,同样拥有密钥和公钥。CA的公钥通常已经被内置到IE或者Netscape这样的软件,它使用自己的私钥对其它人的证书签名。理论上任何人都可以成为CA,为其它人的证书进行签名认证。但是,因为要求CA要被交易双方所信任,双方都必须包含有CA的信任记录(即证书),所以在Internet范围内的应用通常会选择Verisign等内置于IE或者Netscape的CA机构,当然,要交一笔不菲的美金。证书与CA的关系可以参照公民与公安部的关系。公民之间通过身份证相互认证,而公安部作为公民都信任的第三方签发携带公民身份信息的身份证。在前文SSL的概述中,服务器与客户端双方就是通过交换被CA签名的证书来验证对方的身份。

3 实践数字证书生成

Java 5集成了对SSL的支持,而且提供了一个名为keytool.exe的命令行工具来管理证书与CA签名。keytool.exe位于JRE的bin文件夹下。本文使用%JAVA_HOME%\bin\keytool来指代它。Java 5内置了一些信任的CA证书,它们位于%JAVA_HOME%\lib\security\cacerts[1]文件内,可以使用keytool进行管理。下面是keytool的一些命令行参数:

-genkey

生成新的密钥和公钥并保存(以下是它的参数)

-alias <alias>

在keystore中的名字,一个keystore可以存储多个密钥,每个密钥都有不同的名字,可以使用-alias引用

-keyalg <keyalg>

密钥的生成算法,可以是RSA或者DSA

-keysize <keysize>

密钥长度,512位长的RSA密钥已经被破解,所以推荐个人使用1024位,CA使用2048位

-sigalg <sigalg>

签名算法,可以使用SHA1或者MD5

-dname <dname>

身份信息,X500格式

-validity <valDays>

有效时间,以天为单位

-keypass <keypass>

当前操作的密钥的密码,以防止未授权访问。注意,这个密码和storepass是不一样的,后者是保护整个存储文件的密码。由于很多客户端认为两个密码是一样的,如果设置了不同的密码可能会发生错误

-keystore <keystore>

密钥的存储文件,如前所述,该存储文件将保存用户的公钥和私钥,也可以保存用户信任的证书(包括CA证书和普通证书)。前者称为keyEntry,后者称为trustedCertEntry。此外,可以使用两种格式存储该文件,分别是JKS和PKCS#12[2]。该文件受密码保护。

-storepass <storepass>

keystore的密码。参见-keypass

-storetype <storetype>

存储文件的格式,可以是jks或者pkcs12。参见-keystore

-certreq

生成CA签名请求(以下是它的参数)

-file <csr_file>

签名请求存储文件名

-alias <alias>

-sigalg <sigalg>

-keypass <keypass>

-keystore <keystore> -storepass <storepass>

-storetype <storetype>

参见-genkey

-delete

从存储文件中删除一个记录(以下是它的参数)

-alias <alias> -keystore <keystore>

-storepass <storepass>

-storetype <storetype>

参见-genkey

-export

将密钥导出(以下是它的参数)

-rfc

以RFC1421标准规定的格式导出密钥

-alias <alias>

-file <cert_file>

-keystore <keystore>

-storepass <storepass>

-storetype <storetype>

参见-genkey

-import

将密钥导入存储文件(以下是它的参数)

-noprompt

不提示是否信任证书

-trustcacerts

导入证书时考虑其它证书,包括前文所述的JDK内置的包含信任CA的证书存储文件

-alias <alias>

-file <cert_file>

-keypass <keypass>

-keystore <keystore>

-storepass <storepass>

-storetype <storetype>

参见-genkey

-list

列出密钥存储文件中的密钥(以下是它的参数)

-rfc

-alias <alias>

-keystore <keystore>

-storepass <storepass>

-storetype <storetype>

参见-genkey

-printcert

打印证书文件信息(以下是它的参数)

-file

参见-delete

-selfcert

自签名(以下是它的参数)

-alias <alias>

-dname <dname>

-validity <valDays>

-keypass <keypass>

-sigalg <sigalg>

-keystore <keystore>

-storepass <storepass>

-storetype <storetype>

参见-genkey



[1] 如果安装的是JDK,则对应%JAVA_HOME%\jre\lib\security\cacerts

[2] 支持的格式实际上取决于JDK或者当前已安装的支持软件包

keytool本身虽然可以管理密钥和证书,也能够完全自签名,可以满足普通的应用,但是如果需要根据PKI规范的要求建立证书链,就需要用到另一个工具OpenSSL。OpenSSL的命令行比较复杂。首先从OpenSSL的官方网站下载OpenSSL的编译版本[1]。下载安装后可以看到一个名为openssl.exe的可执行文件,它不需要其它动态链接库,可以将其拷贝到%SystemRoot%,即(C:\Windows\system32\)下。以下是它常用的几条子命令:

genrsa

使用RSA算法生成密钥(以下是它的参数)

-in <keyfile>

密钥输入的文件名

-out <keyfile>

密钥输出的文件名

req

管理CA签名请求(以下是它的参数)

-new

生成CA签名请求

-out <careqfile>

证书文件输出文件名

-key

输入的密钥文件

x509

管理x509证书文件(以下是它的参数)

-req

对CA签名请求进行签名

-in <careqfile>

输入CA签名请求文件      

-out <cacertfile>

签名后的证书文件

-signkey <keyfile>

自签名使用的密钥文件 TODO 在CA签名中的作用待了解

-days

有效期

-CAserial <caserial>

保存CA签名序列号文件

-CAcreateserial

如果没有,创建CA签名序列号

-CAkey <cakey>

CA私钥文件

-CA

CA证书

-sha1

使用SHA1散列算法

-trustout

TODO 待了解

pkcs12

使用PKCS#12格式管理存储文件(以下是它的参数)

-export

导出PKCS#12格式的存储文件

-in <cacertfile/keystore>

证书文件或者存储文件

-clcerts

仅导出客户端证书

-inkey <keyfile>

私钥文件

-password <storepass>

保护存储文件的密码

-out <keystore/cacertfile/keyfile>

存储文件或者证书、密钥

-nodes

不加密私钥

-nokeys

不包含私钥

可以发现,keytool和openssl的命令多而繁。本文提供一个简单的脚本工具,实现了创建CA以及创建由CA签名的证书两个功能。


[1] 大多数Linux一般已经预安装OpenSSL,试着运行输入openssl命令看看

#! /bin/env python
# -*- encoding:gbk -*-

"""
用于生成WebService使用的CA及并且签署证书
"""
import sys,shutil,os,subprocess,getpass

configure={"debug":False}

def quiet_run(cmd,argstr=None):
        nf=file(os.devnull,"rw")
        if configure["debug"]:
                p=subprocess.Popen(cmd,stdin=subprocess.PIPE)
        else:
                p=subprocess.Popen(cmd,stdin=subprocess.PIPE,stdout=nf,stderr=nf)
        if argstr is not None:
                p.stdin.write(argstr)
                p.stdin.write("\n")
                p.stdin.flush()
        p.wait()
def get_indentity():
        """取得用户身份信息"""
        print "请输入你的身份信息,这些信息将被附加到证书上,以便于客户确认您的身份"
        print "只输入英文"
        identity={}
        identity["C"]="CN"
        identity["ST"]=raw_input("请输入您的省份:")
        identity["L"]=raw_input("请输入您的城市:")
        identity["O"]=raw_input("请输入您的单位名称:")
        identity["OU"]=raw_input("请输入您的部门名称:")
        identity["CN"]=raw_input("请输入您的名字:")
        identity["EMAILADDRESS"]=raw_input("请输入您的电子邮箱地址:")

        #连接成OpenSSL要求的X500格式字符串
        subj="".join(["/"+"=".join((key,identity[key])) for key in identity if len(identity[key])>0])
        print "您的身份认证信息是%s"%subj
        print
        return subj
def create_ca():
        #取得用户身份
        subj=get_indentity()
        #要求输入密码和证书文件名
        password=""
        cafile=""
        while password.strip()=="":
                password=raw_input("请输入保护CA证书的密码(明文显示):")
        print "请记录好该密码,如果丢失该密码,将可能面临安全性破坏和重新部署客户端的风险"
        while cafile.strip()=="":
                cafile=raw_input("请输入CA证书的文件名:")

        try:
                quiet_run("openssl genrsa -out __zt_cakey.pem 1024")
                quiet_run("openssl req -new -out __zt_careq.csr -key __zt_cakey.pem -subj %s"%subj)
                quiet_run("openssl x509 -req -in __zt_careq.csr -out __zt_cacert.pem -signkey __zt_cakey.pem -days %s"%configure["days"])
                quiet_run("openssl pkcs12 -export -clcerts -in __zt_cacert.pem -inkey __zt_cakey.pem -out %s -passout stdin"%(cafile,),password)
        finally:
                try:
                        os.unlink("__zt_cakey.pem")
                        os.unlink("__zt_careq.csr")
                        os.unlink("__zt_cacert.pem")
                except:
                        pass
def create_store():
        print "将为服务器/客户端生成并使用CA证书签署的证书文件"
        cafile=""
        while cafile.strip()=="":
                cafile=raw_input("请输入CA证书的文件名:")
        capassword=""
        while capassword.strip()=="":
                capassword=getpass.getpass("请输入CA证书的密码(不回显):")
        storefile=""
        while storefile.strip()=="":
                storefile=raw_input("请输入新证书的文件名:")
        storepassword=""
        while storepassword.strip()=="":
                storepassword=raw_input("请输入保护新证书的密码(明文显示):")
        storetype=""
        cacertfile=""
        while storetype=="":
                print "Java支持两种格式的证书存储格式,一种是Java环境私有的JKS格式,另一种是RFC标准的PKCS#12格式。如果在SUN Java环境下,优先使用JKS格式,而其它环境则优先使用PKCS#12格式"
                answer=raw_input("请选择,1-JKS格式,2-PKCS#12: ")
                if answer=="1":
                        storetype="JKS"
                if answer=="2":
                        storetype="PKCS12"
                        print "因为PKCS#12格式的存储格式不能同时包含CA的证书,生成客户端密钥之后将同时为您导出CA证书。"
                        while cacertfile=="":
                                cacertfile=raw_input("请输入导出CA证书的文件名:")
       
        subj=get_indentity()

        try:
                #生成未加密的CA公钥
                quiet_run("openssl pkcs12 -in %(cafile)s -clcerts -nodes -nokeys -out __zt_cacert.pem.1 -passin stdin"%{"cafile":cafile},capassword)
                #去掉公钥文件的前四行,否则不兼容Java JSSE
                fp=file("__zt_cacert.pem.1")
                for i in range(4):fp.readline()
                buf=fp.read()
                fp.close()
                fp=file("__zt_cacert.pem","w")
                fp.write(buf)
                fp.close()
                #生成未加密的CA密钥
                quiet_run("openssl pkcs12 -in %(cafile)s -clcerts -nodes -out __zt_cafile.pem -passin stdin"%{"cafile":cafile},capassword)
                quiet_run("openssl rsa -in __zt_cafile.pem -out __zt_cakey.pem")
                #生成新证书
                if storetype=="JKS":
                        subj=subj.replace("/",",")[1:]
                        quiet_run("keytool -genkey -alias mykey -keyalg rsa -keysize 1024 -validity %(days)s -keypass %(storepassword)s -storepass %(storepassword)s -keystore %(storefile)s -storetype %(storetype)s -dname %(dname)s"%\
                                  {"days":configure["days"],"storepassword":storepassword,"storefile":storefile,"storetype":storetype,"dname":subj})
                        quiet_run("keytool -certreq -alias mykey -sigalg MD5withRSA -file __zt_myreq.csr -keystore %(storefile)s -storepass %(storepassword)s"%\
                                  {"storepassword":storepassword,"storefile":storefile})
                        quiet_run("openssl x509 -req -in __zt_myreq.csr -out __zt_mycert.pem -CA __zt_cacert.pem -CAkey __zt_cakey.pem -days %(days)s -CAcreateserial -sha1 -trustout -CA __zt_cacert.pem -CAkey __zt_cakey.pem -days %(days)s -CAserial ca-cert.srl -sha1 -trustout"%\
                                  {"days":configure["days"]})
                        quiet_run("keytool -import -alias __zt_caroot -noprompt -keystore %(storefile)s -storepass %(storepassword)s -file __zt_cacert.pem"%\
                                  {"storepassword":storepassword,"storefile":storefile})
                        quiet_run("keytool -import -alias mykey -trustcacerts -noprompt -keystore %(storefile)s -storepass %(storepassword)s -file __zt_mycert.pem"%\
                                  {"storepassword":storepassword,"storefile":storefile})
                        #quiet_run("keytool -delete -alias __zt_caroot -keystore %(storefile)s -storepass %(storepassword)s"%\
                        #          {"storepassword":storepassword,"storefile":storefile})
                elif storetype=="PKCS12":
                        quiet_run("openssl genrsa -out __zt_mykey.pem 1024")
                        quiet_run("openssl req -new -out __zt_myreq.csr -key __zt_mykey.pem -subj %s"%subj)
                        quiet_run("openssl x509 -req -in __zt_myreq.csr -out __zt_mycert.pem -CAkey __zt_cakey.pem -CA __zt_cacert.pem -days %(days)s -CAcreateserial -CAserial ca-cert.srl -sha1 -trustout"%\
                                  {"days":configure["days"]})
                        quiet_run("openssl pkcs12 -export -clcerts -in __zt_mycert.pem -inkey __zt_mykey.pem -out %(storefile)s -passout stdin"%\
                                  {"storefile":storefile},storepassword)
                        os.rename("__zt_cacert.pem",cacertfile)
        except:
                try:
                        os.unlink(storefile)
                except:
                        pass
        finally:
                try:
                        os.unlink("__zt_cacert.pem.1")
                        os.unlink("__zt_cacert.pem")
                        os.unlink("__zt_cafile.pem")
                        os.unlink("__zt_cakey.pem")
                        if storetype=="JKS":
                                os.unlink("__zt_myreq.csr")
                                os.unlink("__zt_mycert.pem")
                        else:
                                os.unlink("__zt_mykey.pem")
                                os.unlink("__zt_myreq.csr")
                                os.unlink("__zt_mycert.pem")
                except:
                        pass


def usage():
        print """使用方法错误
%(cmdname)s newca:
        创建一个CA证书
%(cmdname)s newstore:
        创建一个证书"""%{"cmdname":sys.argv[0]}
        sys.exit(1)
       
def main(argv):
        configure["days"]=365*3
        if len(argv)<2:
                usage()
        if argv[1]=="newca":
                create_ca()
        elif argv[1]=="newstore":
                create_store()
               
       
if __name__=="__main__":

    main(sys.argv)


接下去我们将使用这个工具生成CA证书以及WebService客户端与服务器的证书。

1.       生成CA证书

D:\Goldfish\workon\temp>keymgr.py newca

请输入你的身份信息,这些信息将被附加到证书上,以便于客户确认您的身份

只输入英文

请输入您的省份:Fujian

请输入您的城市:Xiamen

请输入您的单位名称:fish

请输入您的部门名称:DD

请输入您的名字:ca

请输入您的电子邮箱地址:nobody@nowhere.com

您的身份认证信息是/C=CN/CN=ca/L=Xiamen/O=fish/ST=Fujian/EMAILADDRESS=nobody@now

ere.com/OU=DD

 

请输入保护CA证书的密码(明文显示):123456

请记录好该密码,如果丢失该密码,将可能面临安全性破坏和重新部署客户端的风险

请输入CA证书的文件名:fish.pfx

2.       生成客户端证书

D:\Goldfish\workon\temp>keymgr.py newstore

将为服务器/客户端生成并使用CA证书签署PKCS12格式的证书文件

请输入CA证书的文件名:fish.pfx

请输入CA证书的密码(不回显):

请输入新证书的文件名:client.store

请输入保护新证书的密码(明文显示):123456

Java支持两种格式的证书存储格式,一种是Java环境私有的JKS格式,另一种是RFC标准的PK

CS#12格式

请选择,1-JKS格式,2-PKCS#12:1

请输入你的身份信息,这些信息将被附加到证书上,以便于客户确认您的身份

只输入英文

请输入您的省份:Fujian

请输入您的城市:Xiamen

请输入您的单位名称:fish

请输入您的部门名称:DD

请输入您的名字:client

请输入您的电子邮箱地址:

您的身份认证信息是/C=CN/CN=client/L=Xiamen/O=fish/ST=Fujian/OU=DD

3.       生成服务器证书,注意,服务器证书填写的名字要与服务器的域名一样,否则可能被某些客户端自动拒绝

D:\Goldfish\workon\temp>keymgr.py newstore

将为服务器/客户端生成并使用CA证书签署PKCS12格式的证书文件

请输入CA证书的文件名:fish.pfx

请输入CA证书的密码(不回显):

请输入新证书的文件名:server.store

请输入保护新证书的密码(明文显示):123456

Java支持两种格式的证书存储格式,一种是Java环境私有的JKS格式,另一种是RFC标准的PK

CS#12格式

请选择,1-JKS格式,2-PKCS#12:1

请输入你的身份信息,这些信息将被附加到证书上,以便于客户确认您的身份

只输入英文

请输入您的省份:Fujian

请输入您的城市:Xiamen

请输入您的单位名称:fish

请输入您的部门名称:DD

请输入您的名字:server

请输入您的电子邮箱地址:

您的身份认证信息是/C=CN/CN=server/L=Xiamen/O=fish/ST=Fujian/OU=DD

现在,当前工作目录下可以找到fish.pfx、client.store和server.store三个文件。其中fish.pfx是PKCS#12格式的CA密钥存储文件,client.store和server.store是经过CA签名的JKS格式的密钥存储文件,密钥在两者中都名为mykey。

4 配置Tomcat的SSL支持

使Tomcat是很简单的事,只需要对%CATALINA_HOME%\conf\server.xml进行简单的配置。在<Service/>元素里添加一个子元素<Connector/>,内容如下:

<Connector port="443"maxHttpHeaderSize="8192" maxThreads="150"minSpareThreads="25" maxSpareThreads="75" enableLookups="false"disableUploadTimeout="true" acceptCount="100"scheme="https" secure="true" clientAuth="true"sslProtocol="TLS" keystoreFile="D:\Goldfish\workon\server.store"keystorePass="123456" keystoreType="jks"keyAlias="mykey" truststoreFile="D:\Goldfish\workon\server.store"truststorePass="123456" truststoreType="jks"/>

其中几个属性与SSL支持有关,它们的作用描述如下:

scheme

指定为https

secure

指定为true

clientAuth

是否认证客户端证书,如果为true会要求客户端提交它的证书,本例中是双向认证,所以设为true

sslProtocol

指定为TLS

keystoreFile

存储服务器密钥的存储文件

keystorePass

存储服务器密钥的存储文件密码

keystoreType

存储服务器密钥的存储文件的文件格式,可以为jks,也可以为pkcs12

keyAlias

服务器密钥在存储文件中的名字。使用keymgr.py工具生成的密钥存储文件默认是mykey

truststoreFile

存储信任的证书文件的存储文件

truststorePass

存储信任的证书文件的存储文件密码

truststoreType

存储信任的证书文件的存储文件的文件格式。jks和pkcs12之一

port

服务端口,默认的https端口号是443

重启Tomcat之后可以Web Service即可生效。可以使用浏览器进行测试。先把clientAuth改为false,在浏览器的地址栏里输入https://server/cxf/UserService?wsdl,如果可以显示WSDL文档,说明服务器的证书可以工作。再将clientAuth改成true。再次打开https://server/cxf/UserService?wsdl,如果浏览器提示选择一个证书以继续浏览,说明SSL支持已经配置成功。

在实际的生产环境中还应去除原有的不安全连接方法。方法是在该Web应用程序的web.xml文件中增加以下配置:

<web-app>

<security-constraint>

<web-resource-collection>

<web-resource-name>ProtectedContext</web-resource-name>

<url-pattern>/*</url-pattern>

</web-resource-collection>

<user-data-constraint>

<transport-guarantee>CONFIDENTIAL</transport-guarantee>

</user-data-constraint>

</security-constraint>

</web-app>

5 为Web Service客户端启用SSL支持

虽然CXF声称可以通过简单的配置支持SSL客户端,但是根据其用户手册操作时却碰到问题。本文利用CXF提供的辅助类,在客户端向服务器发起SSL连接之前,将客户端证书与CA证书的存储文件配置设置到CXF内部。代码如下:

UserServiceFactory.java:

import java.io.IOException;

import java.io.InputStream;

importjava.net.MalformedURLException;

import java.net.URL;

import java.security.KeyStore;

importjava.security.KeyStoreException;

importjava.security.NoSuchAlgorithmException;

importjava.security.UnrecoverableKeyException;

importjava.security.cert.CertificateException;

 

import javax.net.ssl.KeyManager;

importjavax.net.ssl.KeyManagerFactory;

importjavax.net.ssl.TrustManager;

importjavax.net.ssl.TrustManagerFactory;

import javax.xml.namespace.QName;

 

importorg.apache.cxf.configuration.jsse.TLSClientParameters;

importorg.apache.cxf.endpoint.Client;

importorg.apache.cxf.frontend.ClientProxy;

importorg.apache.cxf.transport.http.HTTPConduit;

importorg.apache.cxf.transports.http.configuration.HTTPClientPolicy;

 

import demo.cxf.User;

import demo.cxf.UserService;

importdemo.cxf.UserServiceService;

 

public class UserServiceFactory {

       privatefinal static String keyStore = "client.store";

       privatefinal static String trustStore = "client.store";

       privatefinal static String trustStorePass = "123456";

       privatefinal static String keyStorePass = "123456";

       privatefinal static QName SERVICE = new QName("http://cxf.demo/","UserServiceService");

       privatestatic UserService us;

       /**

        * 取得信任证书管理器

        * @return

        * @throws IOException

        */

       privatestatic TrustManager[] getTrustManagers() throws IOException {

              try{

                     Stringalg = TrustManagerFactory.getDefaultAlgorithm();

                     TrustManagerFactoryfactory = TrustManagerFactory.getInstance(alg);

                     InputStreamfp = UserServiceFactory.class.getResourceAsStream(trustStore);

                     KeyStoreks = KeyStore.getInstance("JKS");

                     ks.load(fp,trustStorePass.toCharArray());

                     fp.close();

                     factory.init(ks);

                     TrustManager[]tms = factory.getTrustManagers();

                     returntms;

              }catch (NoSuchAlgorithmException e) {

                     e.printStackTrace();

              }catch (KeyStoreException e) {

                     e.printStackTrace();

              }catch (CertificateException e) {

                     e.printStackTrace();

              }

              returnnull;

       }

 

       /**

        * 取得个人证书管理器

        * @return

        * @throws IOException

        */

       privatestatic KeyManager[] getKeyManagers() throws IOException {

              try{

                     Stringalg = KeyManagerFactory.getDefaultAlgorithm();

 

                     KeyManagerFactoryfactory = KeyManagerFactory.getInstance(alg);

                     InputStreamfp =UserServiceFactory.class.getResourceAsStream(keyStore);

                     KeyStoreks = KeyStore.getInstance("JKS");

                     ks.load(fp,keyStorePass.toCharArray());

                     fp.close();

                     factory.init(ks,keyStorePass.toCharArray());

                     KeyManager[]keyms = factory.getKeyManagers();

                     returnkeyms;

 

              }catch (NoSuchAlgorithmException e) {

                     e.printStackTrace();

              }catch (KeyStoreException e) {

                     e.printStackTrace();

              }catch (CertificateException e) {

                     e.printStackTrace();

              }catch (UnrecoverableKeyException e) {

                     e.printStackTrace();

              }

              returnnull;

       }

 

       static{

              UserServiceServiceservice = null;

              try{

                     service= new UserServiceService(new URL("file:D:\\ws\\UserServiceService.wsdl"),

                                   SERVICE);

              }catch (MalformedURLException e) {

                     e.printStackTrace();

              }

              us= service.getUserServicePort();

 

              Clientclient = ClientProxy.getClient(us);

              HTTPConduithttpConduit = (HTTPConduit) client.getConduit();

 

              TLSClientParameterstlsParams = httpConduit.getTlsClientParameters();

              if(tlsParams == null)

                     tlsParams= new TLSClientParameters();

                     tlsParams.setSecureSocketProtocol("SSL");

              try{

                     tlsParams.setKeyManagers(getKeyManagers());

                     tlsParams.setTrustManagers(getTrustManagers());

              }catch (IOException e) {

                     e.printStackTrace();

              }

              httpConduit.setTlsClientParameters(tlsParams);

       }

      

       publicstatic UserService getInstance(){

              returnus;

       }

}

这些代码一目了解,不再进行深入的解释。在实际使用中只需酌情修改存储文件的位置、密码、服务名、与WSDL文件地址及由WSDL文档生成的接口或者类名。作为示例,要把client.store放到${CLASSPATH}下,最简单的就是放到与UserServiceFactory相同的位置。

使用了上述工厂模式之后,可以写出这样的测试代码:

UserServiceus=UserServiceFactory.getInstance();

User u=new User();

u.setUsername("fish");

us.createUser(u);

try {

       us.deleteUser("fish");

} catch(NotFoundException_Exception e) {

       e.printStackTrace();

}

上述代码将会在服务器端打印出

createUser

fish

并且在客户端得到一个NotFoundException_Exception异常

6 吊销客户端证书

在生产环境中,不可避免地要碰到与客户中止合作,并取消其访问权限的情况。这通常表现为服务器端显式拒绝客户端的连接请求。基本思路是设置一个包含所有已注销客户的黑名单。但J2EE与CXF并没有提供对证书黑名单的支持。这时,我们需要使用CXF的插件功能,为CXF开发过滤插件。

在在之前,首先了解一下CXF的系统架构。简单地说,CXF使用流水线型(或者说总线型)处理机制,它的核心是一个Bus。一个客户端的请求或者一个对客户端桩代码的调用被组织成为一个Message。同时,所有的CXF功能都组织成Interceptor挂接在Bus上,分阶段依次处理Message。Message本质上是一个Map数据结构,既包含系统公共的也包含Interceptor自定义的数据。

要提供证书黑名单功能,首先要取得CXFServlet提供的HttpServletRequest对象,从中取得证书包含的身份信息,对其进行验证。代码如下:

package demo.cxf;

 

importjava.security.cert.X509Certificate;

 

importjavax.security.auth.x500.X500Principal;

importjavax.servlet.http.HttpServletRequest;

 

import org.apache.cxf.bus.CXFBusImpl;

importorg.apache.cxf.interceptor.Fault;

importorg.apache.cxf.message.Message;

importorg.apache.cxf.phase.AbstractPhaseInterceptor;

importorg.apache.cxf.phase.Phase;

importorg.apache.cxf.transport.http.AbstractHTTPDestination;

 

public class SSLFilter extendsAbstractPhaseInterceptor<Message> {

       publicSSLFilter() {

              super(Phase.RECEIVE);

       }

       privateCXFBusImpl bus;

       publicCXFBusImpl getBus() {

              returnbus;

       }

       publicvoid setBus(CXFBusImpl bus) {

              this.bus= bus;

       }

       publicvoid handleMessage(Message msg) throws Fault {

              HttpServletRequestrequest = (HttpServletRequest) msg.get(AbstractHTTPDestination.HTTP_REQUEST);

              if(request == null)

                     return;

              StringcertSubject = null;

              X509Certificate[]certChain = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");

              if(certChain == null) {

                     System.out.println("nojavax.servlet.request.X509Certificate instance");

              }else {

                     intlen = certChain.length;

                     if(len > 0) {

                            X509Certificatecert = (X509Certificate) certChain[0];

                            X500PrincipalpSubject = cert.getSubjectX500Principal();

                            certSubject= pSubject.getName();

                            //判断客户的名字是否出现在吊销列表中,如果是的话,抛出异常

                            if(certSubject.indexOf("client2") != -1)

                                   thrownew Fault(new RuntimeException("fish is here!"));

                     }

                     System.out.println(certSubject);

              }

       }

}

写完代码之后,要将这个Interceptor插接到CXF,方法是修改CXF的配置文件WEB-INF/beans.xml,如下(注意红色修改部分):

<beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:jaxws="http://cxf.apache.org/jaxws"

       xsi:schemaLocation="

http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd

http://cxf.apache.org/jaxwshttp://cxf.apache.org/schemas/jaxws.xsd">

       <importresource="classpath:META-INF/cxf/cxf.xml" />

       <importresource="classpath:META-INF/cxf/cxf-extension-soap.xml" />

       <importresource="classpath:META-INF/cxf/cxf-servlet.xml" />

       <beanid="sslfilter" class="demo.cxf.SSLFilter">

              <propertyname="bus" ref="cxf" />

       </bean>

       <jaxws:endpointid="UserService" implementor="demo.cxf.UserServiceImpl"address="/UserService">

              <jaxws:inInterceptors>

                     <refbean="sslfilter" />

              </jaxws:inInterceptors>

       </jaxws:endpoint>

</beans>

  参考文档:

使用openssl和keytool生成证书:
http://debian.kanxue.net/2007/03/19/tomcat-ssl/


请大家用Firefox阅读本文。晕死,从Word复制过来的东西居然用IE显示得乱七八糟。

此文基于我在公司的部分工作,有版权(大部分在家里写出来的,大方一点,我和公司各一半),但是因为抄了维基百科上的东西,按GPL,本文也是GPL的

附带的Python脚本用于生成CA证书和CA签名后的个人证书,在使用之前要先安装openssl,如果要使用java的jks格式,还要安装jre。要求openssl.exe与keytool.exe在$PATH下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值