类型:Java,创建时间:Dec. 30, 2011, 6:14 p.m.
标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/3/。
本文作者:goldfish@besteam.im,欢迎来我的博客:http://besteam.im/blogs/。版权所有,转载请注明来源。
SSL概述
安全套接层(SSL)及其新继任者传输层安全(TLS)是在互联网上提供保密安全通道的加密协议,为诸如网站、电子邮件、网上传真等等数据传输进行保密。TLS利用密钥算法在互联网上提供端点身份认证与通讯保密。在典型例子中,只有服务器被可靠身份认证(即其验证被确保),客户端踪迹不一定经可靠认证;相互间的身份认证需要公钥基础设施(PKI)设置于客户端中。协议的设计在某种程度上能够使客户端/服务器应用程序通讯本身预防窃听、干扰(Tampering)、和消息伪造。
TLS包含三个基本阶段:
在第一阶段,客户端与服务器协商所用密码算法。 当前广泛实现的算法选择如下:
TLS的记录层(Record layer)用于封装更高层的HTTP等协议。记录层数据可以被随意压缩、加密,与消息验证码(MAC)打包在一起。每个记录层包都有一个content_type段用以记录更上层用的协议。
当一个连接被发起时,从客户端的角度看,要收发几个握手信号:
TLS/SSL有多样的安全保护措施。所有的记录层数据均被编号,序号用在消息验证码(MAC)中。
公开密钥加密、证书与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签名的证书来验证对方的身份。
实践数字证书生成
Java 5集成了对SSL的支持,而且提供了一个名为keytool.exe
的命令行工具来管理证书与CA签名。keytool.exe
位于JRE的bin文件夹下。本文使用%JAVA_HOME%\bin\keytool
来指代它。Java 5内置了一些信任的CA证书,它们位于%JAVA_HOME%\lib\security\cacerts
(如果安装的是JDK,则对应%JAVA_HOME%\jre\lib\security\cacerts
)文件内,可以使用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(支持的格式实际上取决于JDK或者当前已安装的支持软件包)。该文件受密码保护。 |
-storepass <storepass> | keystore的密码。参见-keypass |
-storetype <storetype> | 存储文件的格式,可以是jks或者pkcs12。参见-keystore |
-certreq
生成CA签名请求(以下是它的参数)
参数 | 参数说明 |
-file <csr_file> | 签名请求存储文件名 |
-alias <alias> | 参见-genkey |
-sigalg <sigalg> | |
-keypass <keypass> | |
-keystore <keystore> | |
-storepass <storepass> | |
-storetype <storetype> |
-delete
从存储文件中删除一个记录(以下是它的参数)
参数 | 参数说明 |
-alias <alias> | 参见-genkey |
-keystore <keystore> | |
-storepass <storepass> | |
-storetype <storetype> |
-export
将密钥导出(以下是它的参数)
参数 | 参数说明 |
-rfc | 以RFC1421标准规定的格式导出密钥 |
-alias <alias> | 参见-genkey |
-file <cert_file> | |
-keystore <keystore> | |
-storepass <storepass> | |
-storetype <storetype> |
-import
将密钥导入存储文件(以下是它的参数)
参数 | 参数说明 |
-noprompt | 不提示是否信任证书 |
-trustcacerts | 导入证书时考虑其它证书,包括前文所述的JDK内置的包含信任CA的证书存储文件 |
-alias <alias> | 参见-genkey |
-file <cert_file> | |
-keypass <keypass> | |
-keystore <keystore> | |
-storepass <storepass> | |
-storetype <storetype> |
-list
列出密钥存储文件中的密钥(以下是它的参数)
参数 | 参数说明 |
-alias <alias> | 参见-genkey |
-rfc | |
-keystore <keystore> | |
-storepass <storepass> | |
-storetype <storetype> |
-printcert
打印证书文件信息(以下是它的参数)
参数 | 参数说明 |
-file参见-delete |
|
-selfcert
自签名(以下是它的参数)
参数 | 参数说明 |
-alias <alias> | 参见-genkey |
-dname <dname> | |
-validity <valDays> | |
-keypass <keypass> | |
-sigalg <sigalg> | |
-keystore <keystore> | |
-storepass <storepass> | |
-storetype <storetype> |
keytool本身虽然可以管理密钥和证书,也能够完全自签名,可以满足普通的应用,但是如果需要根据PKI规范的要求建立证书链,就需要用到另一个工具OpenSSL。OpenSSL的命令行比较复杂。首先从OpenSSL的官方网站下载OpenSSL的编译版本(大多数Linux一般已经预安装OpenSSL,试着运行输入openssl命令看看)。下载安装后可以看到一个名为openssl.exe
的可执行文件,它不需要其它动态链接库,可以将其拷贝到%SystemRoot%
,即(C:\Windows\system32\
)下。以下是它常用的几条子命令:
genrsa
使用RSA算法生成密钥(以下是它的参数)
参数 | 参数说明 |
-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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | #! /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}) 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证书以及Web Service客户端与服务器的证书。
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@nowwhere.com/OU=DD 请输入保护CA证书的密码(明文显示):123456 请记录好该密码,如果丢失该密码,将可能面临安全性破坏和重新部署客户端的风险 请输入CA证书的文件名:fish.pfx
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
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。
配置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支持有关,它们的作用描述如下:
属性(attribute) | 属性说明 |
scheme | 协议。有http和https两个取值。这里指定为https |
secure | 是否安全链接。指定为true |
clientAuth | 是否认证客户端证书,如果为true会要求客户端提交它的证书,本例中是双向认证,所以设为true |
sslProtocol | 指定为TLS |
keystoreFile | 存储服务器密钥的存储文件 |
keystorePass | 存储服务器密钥的存储文件密码 |
keystoreType | 存储服务器密钥的存储文件的文件格式,可以为jks ,也可以为pkcs12 |
keyAlias | 服务器密钥在存储文件中的名字。使用keymgr.py 工具生成的密钥存储文件默认是mykey |
truststoreFile | 存储信任的证书文件的存储文件 |
truststorePass | 存储信任的证书文件的存储文件密码 |
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>Protected Context</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>
为Web Service客户端启用SSL支持
虽然CXF声称可以通过简单的配置支持SSL客户端,但是根据其用户手册操作时却碰到问题。本文利用CXF提供的辅助类,在客户端向服务器发起SSL连接之前,将客户端证书与CA证书的存储文件配置设置到CXF内部。代码如下:
//UserServiceFactory.java: import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.xml.namespace.QName; import org.apache.cxf.configuration.jsse.TLSClientParameters; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; import demo.cxf.User; import demo.cxf.UserService; import demo.cxf.UserServiceService; public class UserServiceFactory { private final static String keyStore = "client.store"; private final static String trustStore = "client.store"; private final static String trustStorePass = "123456"; private final static String keyStorePass = "123456"; private final static QName SERVICE = new QName("http://cxf.demo/", "UserServiceService"); private static UserService us; /** * 取得信任证书管理器 * @return * @throws IOException */ private static TrustManager[] getTrustManagers() throws IOException { try{ String alg = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory factory = TrustManagerFactory.getInstance(alg); InputStream fp = UserServiceFactory.class.getResourceAsStream(trustStore); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(fp, trustStorePass.toCharArray()); fp.close(); factory.init(ks); TrustManager[] tms = factory.getTrustManagers(); return tms; }catch (NoSuchAlgorithmException e) { e.printStackTrace(); }catch (KeyStoreException e) { e.printStackTrace(); }catch (CertificateException e) { e.printStackTrace(); } return null; } /** * 取得个人证书管理器 * @return * @throws IOException */ private static KeyManager[] getKeyManagers() throws IOException { try{ String alg = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory factory = KeyManagerFactory.getInstance(alg); InputStream fp =UserServiceFactory.class.getResourceAsStream(keyStore); KeyStore ks = KeyStore.getInstance("JKS"); ks.load(fp, keyStorePass.toCharArray()); fp.close(); factory.init(ks, keyStorePass.toCharArray()); KeyManager[] keyms = factory.getKeyManagers(); return keyms; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (CertificateException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } return null; } static{ UserServiceService service = null; try{ service = new UserServiceService( new URL("file:D:\\ws\\UserServiceService.wsdl"), SERVICE); } catch (MalformedURLException e) { e.printStackTrace(); } us = service.getUserServicePort(); Client client = ClientProxy.getClient(us); HTTPConduit httpConduit = (HTTPConduit) client.getConduit(); TLSClientParameters tlsParams = 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); } public static UserService getInstance(){ return us; } }
这些代码一目了解,不再进行深入的解释。在实际使用中只需酌情修改存储文件的位置、密码、服务名、与WSDL文件地址及由WSDL文档生成的接口或者类名。作为示例,要把client.store
放到${CLASSPATH}
下,最简单的就是放到与UserServiceFactory
相同的位置。
使用了上述工厂模式之后,可以写出这样的测试代码:
UserService us=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异常
吊销客户端证书
在生产环境中,不可避免地要碰到与客户中止合作,并取消其访问权限的情况。这通常表现为服务器端显式拒绝客户端的连接请求。基本思路是设置一个包含所有已注销客户的黑名单。但J2EE与CXF并没有提供对证书黑名单的支持。这时,我们需要使用CXF的插件功能,为CXF开发过滤插件。
在在之前,首先了解一下CXF的系统架构。简单地说,CXF使用流水线型(或者说总线型)处理机制,它的核心是一个Bus。一个客户端的请求或者一个对客户端桩代码的调用被组织成为一个Message。同时,所有的CXF功能都组织成Interceptor挂接在Bus上,分阶段依次处理Message。Message本质上是一个Map数据结构,既包含系统公共的也包含Interceptor自定义的数据。
要提供证书黑名单功能,首先要取得CXFServlet提供的HttpServletRequest对象,从中取得证书包含的身份信息,对其进行验证。代码如下:
package demo.cxf; import java.security.cert.X509Certificate; import javax.security.auth.x500.X500Principal; import javax.servlet.http.HttpServletRequest; import org.apache.cxf.bus.CXFBusImpl; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.apache.cxf.transport.http.AbstractHTTPDestination; public class SSLFilter extends AbstractPhaseInterceptor<Message> { public SSLFilter() { super(Phase.RECEIVE); } private CXFBusImpl bus; public CXFBusImpl getBus() { return bus; } public void setBus(CXFBusImpl bus) { this.bus = bus; } public void handleMessage(Message msg) throws Fault { HttpServletRequest request = (HttpServletRequest) msg.get(AbstractHTTPDestination.HTTP_REQUEST); if (request == null) return; String certSubject = null; X509Certificate[] certChain = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); if (certChain == null) { System.out.println("no javax.servlet.request.X509Certificate instance"); } else { int len = certChain.length; if (len > 0) { X509Certificate cert = (X509Certificate) certChain[0]; X500Principal pSubject = cert.getSubjectX500Principal(); certSubject = pSubject.getName(); // 判断客户的名字是否出现在吊销列表中,如果是的话,抛出异常 if (certSubject.indexOf("client2") != -1) throw new Fault(new RuntimeException("fish is here!")); } System.out.println(certSubject); } } }
写完代码之后,要将这个Interceptor插接到CXF,方法是修改CXF的配置文件WEB-INF/beans.xml,如下(注意红色修改部分):
<beans xmlns="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/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" /> <bean id="sslfilter" class="demo.cxf.SSLFilter"> <property name="bus" ref="cxf" /> </bean> <jaxws:endpoint id="UserService" implementor="demo.cxf.UserServiceImpl" address="/UserService"> <jaxws:inInterceptors> <ref bean="sslfilter" /> </jaxws:inInterceptors> </jaxws:endpoint> </beans>
参考文档:
使用openssl和keytool生成证书:http://debian.kanxue.net/2007/03/19/tomcat-ssl/
此文基于我在公司的部分工作,有版权(大部分在家里写出来的,大方一点,我和公司各一半),但是因为抄了维基百科上的东西,按GPL,本文也是GPL的
附带的Python脚本用于生成CA证书和CA签名后的个人证书,在使用之前要先安装openssl,如果要使用java的jks
格式,还要安装jre。要求openssl.exe
与keytool.exe
在$PATH下
标题无“转载”即原创文章,版权所有。转载请注明来源:http://hgoldfish.com/blogs/article/3/。
fxhnkf(Jan. 2, 2012, 8:21 a.m.)
not bad