当Web服务交换业务数据时,安全性至关重要。 如果数据被第三方拦截或欺诈性数据被接受为有效数据,则可能导致负面的财务或法律后果。 始终可以为Web服务设计和实现应用程序自己的安全处理(就像任何形式的数据交换一样),但这是一种冒险的方法,因为即使是细微而模糊的监督也可能导致严重的漏洞。 与更简单的数据交换形式相比,SOAP的主要优点之一是它允许模块化扩展。 自从SOAP最初发布以来,安全性一直是扩展的主要关注点,这导致WS-Security和相关技术的标准化,从而可以为每个服务适当地配置安全性。
信息交换的安全性要求通常包括三个方面:
- 机密性 :仅邮件的预期收件人有权访问邮件内容。
- 完整性 :接收到的消息未经修改。
- 真实性 :可以验证消息的来源。
WS-Security使您可以轻松解决所有三个方面。 在本文中,您将看到如何使用Axis2和Rampart WS-Security扩展来执行此操作。 但是首先,我将简要介绍一下公共密钥加密的原理,这是大多数WS-Security的加密和签名功能的基础。
公钥加密
在整个历史过程中,安全的消息交换一直基于某种形式的共享机密。 机密可以采取代码的形式,其中交换双方具有一组商定的短语或动作替代词。 也可以是密码,其中使用某种算法将文本转换为其他文本。 机密甚至可以采取其他形式,例如可能会访问消息的其他方不知道的语言。 共享的机密使消息交换变得安全。 但是,如果另一方发现了秘密,则消息交换将受到损害,从而可能给交换双方带来灾难性的后果。 (例如,想想谜语和第二次世界大战中的德国军事通信。)
公钥密码学是一种本质上与众不同的安全方法,不需要共享密钥。 它基于数学上的“活板门”函数的思想,该函数易于在一个方向上计算,但在反向上却很难计算。 例如,很容易找到两个质数的乘积(如果您在计算机上工作,则甚至是非常大的质数),但要分析该乘积以找到原始的两个因素则要困难得多。 如果围绕函数的简单方向构建加密算法,那么任何想破坏您的加密的人都需要从硬的角度进行工作。 而且,通过精心选择的算法,破解加密任务变得非常困难,以至于在威胁消息交换的时间范围内这样做是不切实际的(至少要等到有人开发出可用的量子计算机或真正好的心理能力之后) )。
使用公钥加密,希望接收加密消息的一方会创建一对密钥值 。 每个密钥值可以分别用于加密消息,但不能用于解密用于加密的消息。 相反,必须使用该对中的另一个密钥进行解密。 只要密钥的所有者对其中一个密钥保密,就可以将另一个密钥公开。 有权访问公钥的任何人都可以使用它来加密消息,然后只能由密钥所有者对消息进行解密。 由于使用单独的密钥来加密和解密消息,因此这种加密形式称为非对称加密 。
签名消息
当您的公共密钥用于加密消息时,只有您(作为私有密钥的持有者)才能解密该消息。 这样可以确保机密性,这是安全消息交换的三个方面之一。 但它也可以使用您的私钥对消息进行加密,而当这是你的公共密钥的副本来实现任何人都可以解密消息。 从表面上看,这似乎不是很有用-任何人都可以阅读的加密消息有什么用? —但它很好地用作验证消息真实性的一种方式。 有人收到了您发送来的加密消息,可以使用您的公钥解密该消息并将其与某个期望值进行比较。 如果结果匹配,则说明您创建了该消息。
实际上,签名消息中涉及的不只是使用私钥进行加密。 一方面,您需要某种方式来建立解密消息的期望值。 通常使用另一种数学陷阱门函数来完成此操作:哈希函数易于计算但难以复制(这意味着很难在不更改该消息的哈希值的情况下对消息进行任何更改或查找另一个消息具有与提供的消息相同的哈希值)。 使用这样的哈希函数,您可以为要签名的消息生成哈希值(在安全性讨论中通常称为摘要 ),然后使用私钥对该摘要进行加密,并随消息发送加密的摘要值。 接收该消息的任何人都可以对该消息使用相同的哈希算法,然后使用您的公共密钥解密提供的加密摘要,并比较这两个值。 如果值匹配,则收件人可以确定(在当前技术的限制内,并且假设您已将私钥保密),该消息是来自您的。
在处理XML时,对消息进行签名的另一步骤。 XML消息以文本形式表示,但是XML认为文本表示的某些方面不相关(例如元素上的属性顺序或元素开始或结束标记内使用的空白)。 由于存在文本表示形式的问题,W3C(XML规范的所有者)决定在计算摘要值之前将XML消息转换为规范的文本形式(请参阅参考资料 )。 定义了几种可与XML消息一起使用的规范化算法。 只要参与消息交换的双方都同意使用相同的算法,通常使用的特定算法就没什么关系。
使用签名的消息摘要可同时保证消息的完整性(因为对消息的修改将更改摘要的值)和真实性(因为您的私钥用于加密摘要)。 由于使用公钥的加密可以确保发送给您的邮件的机密性,因此,使用公钥-私钥对可以覆盖消息交换安全性的所有主要方面。 当然,使用单个密钥对,只能保证消息交换的一个方向,但是,如果交换的另一方也具有自己的公共/私有密钥对,则可以为在每个方向上传递的消息提供完全的安全性。
证明书
因此,公私钥对可用于加密和签名两方之间交换的消息,前提是每一方都可以访问另一方的公钥。 剩下的问题就是如何以维护安全性的方式获取公钥。 在执行此操作的各种方法中,最广泛使用的方法是让一个或多个受信任的第三方担保公共密钥。 数字证书是以这种凭证形式提供公共密钥的机制。
数字证书基本上是围绕公钥的包装,其中包括拥有该公钥的一方的标识信息。 然后,该包装的正文由受信任的第三方签名,并且签名包括在证书中。 受信任的第三方通过颁发带有签名的证书来担保公共密钥和标识信息。 当然,这留下了如何建立受信任的第三方身份的引导问题。 通常,通过对某些受信任的第三方(称为发行机构)的证书进行硬编码证书来完成软件(例如JVM)。
除了这里描述的基础知识之外,数字证书还有很多其他功能,包括撤销错误(不幸的是,发生)或私钥泄露的证书的方法,证书有效的时间跨度以及用于指定预期用途的扩展名证书。 请参阅相关主题的更多信息的链接在一般的数字证书和公钥加密。 您还可以查看JDK安装中包含的keytool安全工具的文档。 keytool文档很好地介绍了证书的结构和处理,以及密钥库 (下面讨论)和安全性的其他方面。 在本文后面的部分中,您还将看到使用keytool的示例。
密钥库
大多数Java安全代码都使用密钥库来处理私钥和数字证书。 密钥库只是一个文件,其中包含加密形式的密钥和证书条目。 访问密钥库需要密码。 密钥库中的每个私钥再次被加密,需要一个额外的密码,以帮助维护密钥的安全性。 使用密钥库和私钥的任何软件都需要在运行时同时具有密钥库和私钥密码。 这限制了这些密码提供的安全性(因为任何有权访问源代码的人都可以找到密码的加载方式)。 因此,您需要维护托管该软件的系统以及该系统的所有备份的物理安全性。 而且,您应该仅在该系统和那些备份上保留密钥库和密码,以确保私钥的安全。
秘密密钥和对称加密
尽管使用非对称加密的公钥密码学是WS-Security的许多有用功能的基础,但是老式的秘密密钥密码学仍然扮演着重要的角色。 非对称加密算法比基于秘密密钥的对称算法(因此,相同的密钥用于加密和解密,这意味着必须始终将密钥保持秘密)的计算强度要大得多,以实现同等程度的保护。 因此,通常将两种技术结合使用:高成本的非对称加密用于保护密钥的交换,然后可以将其用于低成本的对称加密。 当您查看消息的WS-Security加密时,您将在本文后面看到这种方法的示例。
配置
本文使用与“ Axis2 WS-Security basics ”相同的示例应用程序,它显示了如何使用Axis2和Rampart来实现WS-Security UsernameToken
。 但是,需要进行一些更改以支持使用WS-Security公钥加密功能,因此本文附带了一个单独的代码包(请参阅参考资料 )。
示例代码的根目录是jws05code。 在此目录中,您会发现:
- 一个Ant build.xml文件
- Ant build.properties文件,用于配置示例应用程序的操作
- 该library.wsdl文件提供了示例应用程序的服务定义
- 一个log4j.properties文件,用于配置客户端日志记录
- 几个属性定义XML文件(都命名为XXX -policy-client.xml或XXX -policy-server.xml)
在使用示例代码之前,您需要:
- 编辑build.properties文件以设置Axis2安装的路径。
- 确保已使用Rampart代码更新了Axis2安装,如“ Axis2 WS-Security基础知识 ”的“ 安装Rampart”部分中所述。 (检查的一种好方法是在repository / modules目录中查找rampart-xymar模块文件。)
- 添加
org.bouncycastle.jce.provider.BouncyCastleProvider
充气城堡安全提供(必要的公共密钥加密的示例代码使用的功能)到您的JVM安全配置(lib / security中/ java.security文件)(请参阅相关主题 )。 - 添加充气城堡JAR(名为bcprov-jdk的XX - VVV的.jar,其中xx是您的Java版本和VVV是充气城堡代码版本) 同时 Axis2安装的lib目录和您的Axis2服务器应用程序的WEB-INF / lib目录下。
现在,您可以构建示例应用程序,并尝试下一节中介绍的安全示例。
签名消息
与“ Axis2 WS-Security basics ”中的UsernameToken
示例相比,签名需要更多的规范。 你需要:
- 标识用于在每个方向上创建签名的私钥/公钥对,并提供用于访问密钥库和私钥的密码。
- 指定用于XML规范化,摘要生成和实际签名的算法集。
- 指定消息的哪些部分要包含在签名中。
此信息的一部分作为配置数据处理,嵌入在该服务的WS-SecurityPolicy文档中。 其他部分包括在运行时消息交换中。
清单1显示了一个WS-Policy文档,该文档用于配置Axis2客户端对消息进行签名。 (对清单1进行了编辑以适合页面宽度。在示例代码中,全文请参见sign-policy-client.xml。)
清单1.用于签名的WS-Policy / WS-SecurityPolicy(客户端)
<!-- Client policy for signing all messages, with certificates included in each
message -->
<wsp:Policy wsu:Id="SignOnly"
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/AlwaysToInitiator"/>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:TripleDesRsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts xmlns:sp="http://.../ws-securitypolicy/200702">
<sp:Body/>
</sp:SignedParts>
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:user>clientkey</ramp:user>
<ramp:passwordCallbackClass
>com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
<ramp:signatureCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
>JKS</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file"
>client.keystore</ramp:property>
<ramp:property
name="org.apache.ws.security.crypto.merlin.keystore.password"
>nosecret</ramp:property>
</ramp:crypto>
</ramp:signatureCrypto>
</ramp:RampartConfig>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
在清单1中 ,策略中的<sp:AsymmetricBinding>
元素提供了在消息交换中使用公钥加密的基本配置信息。 在此元素内,几个嵌套元素用于配置细节。 <sp:InitiatorToken>
标识用于对从客户端(发起者)到服务器(接收者)的消息进行签名的密钥对,在这种情况下,声明公共密钥将采用X.509证书的形式,并且将与客户端发送的每条消息一起发送( sp:IncludeToken=".../AlwaysToRecipient"
)。 <sp:RecipientToken>
再次使用X.509证书以及服务器中每条消息中包含的证书来标识用于对从服务器到客户端的响应路径上的消息进行签名的密钥对( sp:IncludeToken=".../AlwaysToInitiator"
)。
<sp:AlgorithmSuite>
元素标识用于签名的算法集。 <sp:IncludeTimestamp>
表示每个消息都将使用时间戳(用于防止对服务的重播类型攻击,从而在传输过程中捕获消息,然后重新提交以试图混淆或破坏服务)。 <sp:OnlySignEntireHeadersAndBody>
元素表示,签名将在消息的整个标头或正文上进行,而不是在某些嵌套元素上进行(另一种安全措施,可防止某些类型的重写消息的攻击)。 <sp:SignedParts>
元素标识要签名的消息部分,在本例中为SOAP消息Body
。
清单1 WS-Policy文档的最后一部分提供了Rampart特定的配置信息。 这是“ Axis2 WS-Security基础 ”中使用的Rampart配置的更复杂版本,这次包括<ramp:user>
元素(用于标识将用于对消息签名的密钥)和<ramp:signatureCrypto>
元素,用于配置包含客户端私钥和服务器证书的密钥库。 在运行时,引用的密钥库文件必须存在于类路径中。 在示例应用程序中,密钥库文件在构建过程中被复制到client / bin目录。
密码回调类与“ Axis2 WS-Security基础 ”中使用的类稍有不同。 对于那篇文章,仅在服务器上需要密码回调,并且仅需要验证(对于纯文本UsernameToken
)或设置(对于哈希UsernameToken
)与特定用户名匹配的密码。 对于本文中使用的公钥加密,回调必须提供用于保护密钥库中的私钥的密码。 客户端(为客户端私钥提供密码)和服务器(为服务器私钥提供密码)分别需要回调。 清单2显示了回调的客户端版本:
清单2.客户端密码回调
/**
* Simple password callback handler. This just checks if the password for the private key
* is being requested, and if so sets that value.
*/
public class PWCBHandler implements CallbackHandler
{
public void handle(Callback[] callbacks) throws IOException {
for (int i = 0; i < callbacks.length; i++) {
WSPasswordCallback pwcb = (WSPasswordCallback)callbacks[i];
String id = pwcb.getIdentifer();
int usage = pwcb.getUsage();
if (usage == WSPasswordCallback.DECRYPT ||
usage == WSPasswordCallback.SIGNATURE) {
// used to retrieve password for private key
if ("clientkey".equals(id)) {
pwcb.setPassword("clientpass");
}
}
}
}
}
清单2中的回调被设计为支持使用相同的私钥-公钥对进行签名和解密,因此它检查两种情况并在两种情况下均返回相同的密码。
除了用于客户端的清单1 WS-Policy之外,还有一个与服务器匹配的示例(示例代码中的sign-policy-server.xml),仅在Rampart配置的细节上有所不同。 同样, 清单2的密码回调类的服务器版本仅在标识符值和返回的密码方面有所不同。
运行示例应用程序
build.properties文件中的行引用了示例应用程序要使用的client.policy
和server.policy
文件。 在提供的文件版本中将它们分别设置为sign-policy-client.xml和sign-policy-server.xml,因此您只需要构建应用程序。 您可以通过打开控制台进入jws05code目录并输入ant
来使用Ant。 如果一切都配置正确,则应该在jws05code目录中得到一个library-signencr.aar Axis2服务归档文件。 通过使用Axis2 Administration页面上载.aar文件,将服务部署到Axis2服务器安装中,然后通过在控制台上输入ant run
尝试客户端。 如果一切设置正确,您应该看到如图1所示的输出:
图1.正在运行的应用程序的控制台输出
若要查看邮件的实际WS-Security的信息,你需要使用一个工具,如TCPMon的(参见相关主题 )。 首先建立TCPMon,并从一个端口上接受客户端的连接,然后将其转发到在另一个端口(或另一个主机)上运行的服务器。 然后,您可以编辑build.properties文件,并将主机端口值更改为TCPMon的侦听端口。 如果再次在控制台上输入ant run
,则应该看到正在交换的消息。 清单3显示了一个示例客户端消息捕获:
清单3.从客户端到服务器的第一条消息
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="Timestamp-3753023">
<wsu:Created>2009-04-18T19:26:14.779Z</wsu:Created>
<wsu:Expires>2009-04-18T19:31:14.779Z</wsu:Expires>
</wsu:Timestamp>
<wsse:BinarySecurityToken
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
EncodingType=".../oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"
wsu:Id="CertId-2650016">MIICoDC...0n33w==</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Id="Signature-29086271">
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#Id-14306161">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>SiU8LTnBL10/mDCPTFETs+ZNL3c=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#Timestamp-3753023">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>2YopLipLgBFJi5Xdgz+harM9hO0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>TnUQtz...VUpZcm3Nk=</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-3932167">
<wsse:SecurityTokenReference
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="STRId-25616143">
<wsse:Reference URI="#CertId-2650016"
ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soapenv:Header>
<soapenv:Body
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-14306161">
<ns2:getBook xmlns:ns2="http://ws.sosnoski.com/library/wsdl">
<ns2:isbn>0061020052</ns2:isbn>
</ns2:getBook>
</soapenv:Body>
</soapenv:Envelope>
SOAP消息中的<wsse:Security>
标头包含所有运行时安全配置信息和签名数据。 根据WS-SecurityPolicy配置的要求,出现的第一项是<wsu:Timestamp>
。 时间戳记包含两个时间值:创建时间的时间和终止时间的时间。 在这种情况下,这两个值相隔五分钟,这是Rampart的默认值。 (您可以在Rampart策略配置中更改这些值。)两者之间的时间多少有些随意,但是五分钟是一个合理的值—它足以应付合理数量的时钟偏斜(两者之间的差异)客户端和服务器之间的系统时钟时间),同时仍然足够短,以限制使用该消息进行重放攻击的可能性。 时间戳记之后,安全性标题中的下一项是<wsse:BinarySecurityToken>
。 此安全令牌是客户端证书,采用base64二进制编码形式。
安全标题中的第三项是<ds:Signature>
块,带有三个子元素。 第一个子元素<ds:SignedInfo>
是消息中唯一直接签名的部分。 <ds:SignedInfo>
的第一个子元素标识用于其自身的规范化和签名的算法。 这些之后是签名中包括的每个消息组件的<ds:Reference>
子元素。 每个子<ds:Reference>
元素<ds:Reference>
标识符引用特定的消息组件,并提供应用于该组件的规范化和摘要算法以及结果摘要值。 <ds:SignedInfo>
的其余子元素提供实际的签名值以及对用于验证签名的公钥的引用(在这种情况下,标题中的<wsse:BinarySecurityToken>
包含的证书,如wsu:Id="CertId-2650016"
所标识) wsu:Id="CertId-2650016"
)。
加密和签名消息
向签名的消息交换添加加密很容易,只需在策略中添加<sp:EncryptedParts>
元素以标识要签名的组件,以及一些其他Rampart配置信息。 清单4显示了用于此目的的策略的客户端版本(再次编辑以适合页面宽度-全文请参见示例代码中的signencr-policy-client.xml文件),并在清单1中添加了策略1 。胆大:
清单4.用于签名然后加密的WS-Policy / WS-SecurityPolicy(客户端)
<!-- Client policy for first signing and then encrypting all messages, with the
certificate included in the message from client to server but only a thumbprint
on messages from the server to the client. -->
<wsp:Policy wsu:Id="SignEncr"
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsp:ExactlyOne>
<wsp:All>
<sp:AsymmetricBinding
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702">
<wsp:Policy>
<sp:InitiatorToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/AlwaysToRecipient"/>
</wsp:Policy>
</sp:InitiatorToken>
<sp:RecipientToken>
<wsp:Policy>
<sp:X509Token
sp:IncludeToken="http://.../IncludeToken/Never">
<wsp:Policy>
<sp:RequireThumbprintReference/>
</wsp:Policy>
</sp:X509Token>
</wsp:Policy>
</sp:RecipientToken>
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:TripleDesRsa15/>
</wsp:Policy>
</sp:AlgorithmSuite>
<sp:Layout>
<wsp:Policy>
<sp:Strict/>
</wsp:Policy>
</sp:Layout>
<sp:IncludeTimestamp/>
<sp:OnlySignEntireHeadersAndBody/>
</wsp:Policy>
</sp:AsymmetricBinding>
<sp:SignedParts xmlns:sp="http://.../200702">
<sp:Body/>
</sp:SignedParts>
<sp:EncryptedParts xmlns:sp="http://.../200702">
<sp:Body/>
</sp:EncryptedParts>
<ramp:RampartConfig xmlns:ramp="http://ws.apache.org/rampart/policy">
<ramp:user>clientkey</ramp:user>
<ramp:encryptionUser>serverkey</ramp:encryptionUser>
<ramp:passwordCallbackClass
>com.sosnoski.ws.library.adb.PWCBHandler</ramp:passwordCallbackClass>
<ramp:signatureCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
>JKS</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file"
>client.keystore</ramp:property>
<ramp:property
name="org.apache.ws.security.crypto.merlin.keystore.password"
>nosecret</ramp:property>
</ramp:crypto>
</ramp:signatureCrypto>
<ramp:encryptionCrypto>
<ramp:crypto provider="org.apache.ws.security.components.crypto.Merlin">
<ramp:property name="org.apache.ws.security.crypto.merlin.keystore.type"
>JKS</ramp:property>
<ramp:property name="org.apache.ws.security.crypto.merlin.file"
>client.keystore</ramp:property>
<ramp:property
name="org.apache.ws.security.crypto.merlin.keystore.password"
>nosecret</ramp:property>
</ramp:crypto>
</ramp:encryptionCrypto>
</ramp:RampartConfig>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
为了使用加密, 清单4策略中的第一项更改不是必需的 ,但这是一个好主意。 使用加密时,客户端在发送初始请求时需要使服务器证书可用(因为来自证书的服务器公钥用于加密)。 由于客户端仍然需要获得服务器证书,因此从来没有理由将服务器证书发送给客户端。 清单4策略中更改后的<sp:RecipientToken>
反映了这种用法,表示不应发送证书( sp:IncludeToken=".../Never"
)和指纹引用 (基本上是证书的哈希值)应该改为使用。 指纹参考比完整证书要紧凑得多,因此使用参考可以减少消息大小和处理开销。
实际实现加密的更改是添加的<sp:EncryptedParts>
元素。 该元素表示要使用加密,而内容<sp:Body>
元素表示SOAP消息主体是要加密的消息的一部分。
清单4中添加的Rampart配置信息包括一个<ramp:encryptionUser>
元素(提供用于加密消息的公用密钥(即证书)的别名)和一个<ramp:encryptionCrypto>
元素,该元素告诉如何访问包含证书的密钥库。 在示例应用程序中,用于签名的私钥和用于加密的公钥都使用相同的密钥库,因此<ramp:encryptionCrypto>
元素只是现有<ramp:signatureCrypto>
元素的重命名副本。
在运行时,Rampart需要获取用于保护私钥的密码,以用于解密加密数据。 如清单2所示,先前用于获取签名的私钥密码的密码回调还提供了用于解密的密码,因此在此无需更改。
运行示例应用程序
要尝试先使用签名然后进行加密的示例应用程序,首先需要编辑build.properties文件。 将客户端策略行更改为client.policy=signencr-policy-client.xml
,将服务器策略更改为server-policy=signencr-policy-server.xml
。 然后,您可以通过运行ant
来重建应用程序,将生成的library-signencr.aar文件部署到Axis2安装中,然后运行ant run
进行尝试。
清单5显示了使用签名后再进行加密时的请求消息捕获,与清单3的纯签名版本以粗体显示了很大的不同:
清单5.使用签名和加密的消息
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<soapenv:Header>
<wsse:Security xmlns:wsse=".../oasis-200401-wss-wssecurity-secext-1.0.xsd"
soapenv:mustUnderstand="1">
<wsu:Timestamp xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="Timestamp-4067003">
<wsu:Created>2009-04-21T23:15:47.701Z</wsu:Created>
<wsu:Expires>2009-04-21T23:20:47.701Z</wsu:Expires>
</wsu:Timestamp>
<xenc:EncryptedKey Id="EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference>
<wsse:KeyIdentifier
EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://.../oasis-wss-soap-message-security-1.1#ThumbprintSHA1"
>uYn3PK2wXheN2lLZr4n2mJjoWE0=</wsse:KeyIdentifier>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>OBUcMI...OIPQEUQaxkZps=</xenc:CipherValue>
</xenc:CipherData>
<xenc:ReferenceList>
<xenc:DataReference URI="#EncDataId-28290629"/>
</xenc:ReferenceList>
</xenc:EncryptedKey>
<wsse:BinarySecurityToken
xmlns:wsu="http://.../oasis-200401-wss-wssecurity-utility-1.0.xsd"
EncodingType="http://...-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://.../oasis-200401-wss-x509-token-profile-1.0#X509v1"
wsu:Id="CertId-2650016">MIICo...QUBCPx+m8/0n33w==</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
Id="Signature-12818976">
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#Id-28290629">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>5RQy7La+tL2kyz/ae1Z8Eqw2qiI=</ds:DigestValue>
</ds:Reference>
<ds:Reference URI="#Timestamp-4067003">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>GAt/gC/4mPbnKcfahUW0aWE43Y0=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>DhamMx...+Umrnims=</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-31999426">
<wsse:SecurityTokenReference
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd"
wsu:Id="STRId-19267322">
<wsse:Reference URI="#CertId-2650016"
ValueType=".../oasis-200401-wss-x509-token-profile-1.0#X509v1"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</soapenv:Header>
<soapenv:Body
xmlns:wsu=".../oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="Id-28290629">
<xenc:EncryptedData Id="EncDataId-28290629"
Type="http://www.w3.org/2001/04/xmlenc#Content">
<xenc:EncryptionMethod
Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<wsse:SecurityTokenReference
xmlns:wsse="http://.../oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:Reference URI="#EncKeyId-urn:uuid:6E12E251E439C034FA12403557497352"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
<xenc:CipherData>
<xenc:CipherValue>k9IzAEG...3jBmonCsk=</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</soapenv:Body>
</soapenv:Envelope>
第一个区别是安全标头中存在<xenc:EncryptedKey>
元素。 该元素的内容使用服务器的公共密钥进行加密,从而以加密形式给出一个秘密密钥。 第二个区别是实际的SOAP Body
内容,已由<xenc:EncryptedData>
元素替换。 此加密的数据元素引用安全性标头中的<xenc:EncryptedKey>
值作为Body
内容上使用的对称加密的密钥。
使用您自己的自签名证书
要获取由公认的证书颁发机构签署的正式数字证书,您需要生成一个公钥-私钥对,并使用该公钥创建证书请求。 然后,您将该证书请求发送给您的首选机构并付款。 权威机构反过来验证您的身份(这是整个过程完整性的重要一步,尽管有时会因令人尴尬的失误而受苦),并颁发带有签名的证书。
为了进行测试或内部使用,您可以生成自己的自签名证书。 本文的示例代码使用两个这样的自签名证书,一个用于客户端,一个用于服务器。 客户端使用的client.keystore密钥库包含客户端的私钥和证书,以及服务器证书(服务器证书必须存储在客户端上,以便在用于以下目的时无需证书授权即可被接受为有效证书)签名,也可以直接用于加密)。 服务器端使用的server.keystore密钥库包含服务器的私钥和证书,以及客户端证书(再次需要,这样证书才能被接受为有效)。
您可以生成自己的私钥和自签名证书,并用生成的密钥证书对替换下载中提供的密钥。 要使用JDK中包含的keytool程序执行此操作,请打开控制台,然后首先输入以下命令行(在此处拆分以适合页面宽度;您必须将其作为一行输入):
keytool -genkey -alias serverkey -keypass serverpass -keyalg RSA -sigalg SHA1withRSA
-keystore server.keystore -storepass nosecret
前面的命令在名为server.keystore的新密钥库中生成服务器密钥和带有别名serverkey
证书。 (如果此目录中已有一个server.keystore,则首先需要使用该别名删除现有的密钥对。)keytool会提示输入用于生成证书的许多信息项(与之无关,在测试),然后要求您批准该信息。 输入yes
,keytool将使用私钥和证书创建密钥库,然后退出。
接下来,执行相同的步骤来创建客户端密钥对和密钥库,这一次使用以下命令行(作为单行输入):
keytool -genkey -alias clientkey -keypass clientpass -keyalg RSA -sigalg SHA1withRSA
-keystore client.keystore -storepass nosecret
下一步是从服务器密钥库中导出证书,并将该证书导入客户端密钥库中。 要导出,请使用以下命令行(在此处拆分以适合页面宽度;您必须将其作为一行输入):
keytool -export -alias serverkey -keystore server.keystore -storepass nosecret
-file servercert.cer
导出将创建一个名为servercert.cer的证书文件,然后您可以使用以下命令行(单行输入)将其导入到客户端密钥库中:
keytool -import -alias serverkey -keystore client.keystore -storepass nosecret
-file servercert.cer
运行import命令时,keytool将打印出证书的详细信息,并询问您是否信任该证书。 通过输入yes
接受密钥后,它将证书添加到密钥库并退出。
对于最后一步,导出客户端证书并将其导入服务器密钥库,方法是首先运行(输入为一行):
keytool -export -alias clientkey -keystore client.keystore -storepass nosecret
-file clientcert.cer
然后运行(在此处拆分以适合页面宽度;您必须将其输入为一行):
keytool -import -alias clientkey -keystore server.keystore -storepass nosecret
-file clientcert.cer
要使用新的密钥和证书,必须在运行客户端构建之前将client.keystore文件复制到示例代码的client / src目录中(或仅将其复制到client / bin目录中以立即生效),并且在运行服务器构建之前,将server.keystore文件放入示例代码的server / src目录中。
本节中的示例keytool命令行使用与提供的示例代码相同的文件名和密码。 您还可以在生成自己的密钥和证书时更改这些值,但是随后还需要更改示例代码以使其匹配。 密钥库密码和文件名是各方策略文件的RampartConfig
部分中的参数。 客户端密钥密码在com.sosnoski.ws.library.adb.PWCBHandler
类的客户端版本中进行硬编码,而服务器密钥密码在同一类的服务器版本中进行硬编码。
Wrapping up
在本文中,您已经了解了如何使用Axis2和Rampart进行基于策略的WS-Security加密和签名。 这些强大的安全功能对于许多类型的业务数据交换都是必不可少的,但是在增加处理开销方面确实要付出一定的代价。 在下一篇Java Web服务文章中,您将看到不同类型的安全性的相对性能成本是如何叠加的,以便您可以更好地判断如何在自己的应用程序中使用安全性。
翻译自: https://www.ibm.com/developerworks/java/library/j-jws5/index.html