本章解释如何将 WS-Security 方面添加到您的 Web 服务中。我们将重点关注 WS-Security 的三个不同领域,即:
认证。 这是确定主体是否是其声称的身份的过程。在此上下文中,“主体”通常指用户、设备或执行应用程序中操作的其他系统。
数字签名。 消息的数字签名是基于文档和签名者私钥的信息。它通过使用哈希函数和私有签名函数(使用签名者的私钥进行加密)创建。
加密和解密。 加密是将数据转换为没有相应密钥就无法读取的形式的过程。它主要用于将信息隐藏起来,不让不应看到它的人看到。解密是加密的逆过程;它是将加密数据转换回可读形式的过程。
所有这三个领域都通过使用 XwsSecurityInterceptor 或 Wss4jSecurityInterceptor 实现,我们将在第 7.2 节,“ XwsSecurityInterceptor ”和第 7.3 节,“ Wss4jSecurityInterceptor ”中分别进行描述
请注意,WS-Security(尤其是加密和签名)需要大量的内存,并且还会降低性能。如果性能对您很重要,您可能需要考虑不使用 WS-Security,或者只使用基于 HTTP 的安全性。
XwsSecurityInterceptor 是一个 EndpointInterceptor(参见第 5.5.2 节,“拦截请求 - EndpointInterceptor 接口”),它基于 SUN 的 XML 和 Web Services Security 包 (XWSS)。此 WS-Security 实现是 Java Web Services Developer Pack (Java WSDP) 的一部分。
像任何其他端点拦截器一样,它在端点映射中定义(参见第 5.5 节,“端点映射”)。这意味着您可以有选择地添加 WS-Security 支持:有些端点映射需要它,而有些则不需要。
请注意,XWSS 需要 SUN 1.5 JDK 和 SUN SAAJ 参考实现。WSS4J 拦截器没有这些要求(参见第 7.3 节,“ Wss4jSecurityInterceptor ”)。
XwsSecurityInterceptor 需要一个安全策略文件才能运行。此 XML 文件告诉拦截器从传入的 SOAP 消息中要求哪些安全方面,以及向传出消息中添加哪些方面。策略文件的基本格式将在以下章节中解释,但您可以在 此处 找到更深入的教程。您可以使用policyConfiguration属性设置策略,该属性需要一个 Spring 资源。策略文件可以包含多个元素,例如要求传入消息中包含用户名令牌,并对所有传出消息进行签名。它包含一个 SecurityConfiguration 元素作为根(而不是 JAXRPCSecurity 元素)。
此外,安全拦截器需要一个或多个 CallbackHandler 才能运行。这些处理程序用于检索证书、私钥、验证用户凭据等。Spring-WS 为大多数常见的安全问题提供处理程序,例如针对 Spring Security 身份验证管理器进行身份验证,基于 X509 证书对传出消息进行签名。以下章节将指出针对哪个安全问题使用哪个回调处理程序。您可以使用 callbackHandler 或 callbackHandlers 属性设置回调处理程序。
这是一个示例,展示了如何连接 XwsSecurityInterceptor
<beans>
<bean id="wsSecurityInterceptor"
class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="certificateHandler"/>
<ref bean="authenticationHandler"/>
</list>
</property>
</bean>
...
</beans>
此拦截器使用类路径上的 securityPolicy.xml 文件进行配置。它使用文件中进一步定义的两个回调处理程序。
对于大多数加密操作,您将使用标准 java.security.KeyStore 对象。这些操作包括证书验证、消息签名、签名验证和加密,但不包括用户名和时间戳验证。本节旨在为您提供有关密钥库的一些背景知识,以及可用于在密钥库文件中存储密钥和证书的 Java 工具。此信息大多与 Spring-WS 无关,而与 Java 的一般加密功能有关。
java.security.KeyStore 类表示用于加密密钥和证书的存储设施。它可以包含三种不同类型的元素
私钥。 这些密钥用于自身份验证。私钥附带相应公钥的证书链。在 WS-Security 领域中,这相当于消息签名和消息解密。
对称密钥。 对称(或秘密)密钥也用于消息加密和解密。区别在于双方(发送方和接收方)共享相同的秘密密钥。
受信任证书。 这些 X509 证书被称为受信任证书,因为密钥库所有者信任证书中的公钥确实属于证书所有者。在 WS-Security 中,这些证书用于证书验证、签名验证和加密。
您的 Java 虚拟机附带了 keytool 程序,这是一个密钥和证书管理实用程序。您可以使用此工具创建新的密钥库,向其中添加新的私钥和证书等。提供 keytool 命令的完整参考超出了本文档的范围,但您可以在 此处 找到参考,或者在命令行上输入命令 keytool -help。
为了使用 Spring 配置轻松加载密钥库,您可以使用 KeyStoreFactoryBean。它有一个资源位置属性,您可以将其设置为指向要加载的密钥库的路径。可以提供密码来检查密钥库数据的完整性。如果未提供密码,则不执行完整性检查。
<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="password" value="password"/>
<property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-keystore.jks"/>
</bean>
如果您未指定位置属性,将创建一个新的空密钥库,这很可能不是您想要的。
要在 XwsSecurityInterceptor 中使用密钥库,您需要定义一个 KeyStoreCallbackHandler。此回调具有三个类型为密钥库的属性(keyStore、trustStore 和 symmetricStore)。处理程序使用的确切存储取决于此处理程序将执行的加密操作。对于私钥操作,使用 keyStore;对于对称密钥操作,使用 symmetricStore;对于确定信任关系,使用 trustStore。下表对此进行了说明
| 加密操作 | 使用的密钥库 |
|---|---|
| 证书验证 | 首先是 keyStore,然后是 trustStore |
| 基于私钥的解密 |
keyStore
|
| 基于对称密钥的解密 |
symmetricStore
|
| 基于公钥证书的加密 |
trustStore
|
| 基于对称密钥的加密 |
symmetricStore
|
| 签名 |
keyStore
|
| 签名验证 |
trustStore
|
此外,KeyStoreCallbackHandler 具有一个 privateKeyPassword 属性,该属性应设置为解锁 keyStore 中包含的私钥。
如果未设置 symmetricStore,它将默认为 keyStore。如果未设置密钥或信任存储,回调处理程序将使用标准 Java 机制加载或创建它。请参阅 KeyStoreCallbackHandler 的 JavaDoc,以了解此机制的工作原理。
例如,如果您想使用 KeyStoreCallbackHandler 来验证传入证书或签名,您将使用信任存储,如下所示
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="trustStore" ref="trustStore"/>
</bean>
<bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:truststore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>如果您想使用它来解密传入证书或签署传出消息,您将使用密钥存储,如下所示
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore"/>
<property name="privateKeyPassword" value="changeit"/>
</bean>
<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:keystore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>
以下部分将指出 KeyStoreCallbackHandler 可用于何处,以及针对特定加密操作要设置哪些属性。
正如引言中所述,身份验证是确定主体是否是其声称的身份的任务。在 WS-Security 中,身份验证可以采用两种形式:使用用户名和密码令牌(使用纯文本密码或密码摘要),或使用 X509 证书。
最简单的用户名认证形式使用纯文本密码。在此场景中,SOAP 消息将包含一个 UsernameToken 元素,其中包含一个 Username 元素和一个包含纯文本密码的 Password 元素。纯文本认证可以与 HTTP 服务器提供的基本认证进行比较。
请注意,纯文本密码的安全性不高。因此,如果您使用它们,应始终为传输层添加额外的安全措施(例如,使用 HTTPS 而不是纯 HTTP)。
为了要求每条传入消息都包含一个带有纯文本密码的 UsernameToken,安全策略文件应包含一个 RequireUsernameToken 元素,其中 passwordDigestRequired 属性设置为 false。您可以在 此处 找到可能的子元素参考。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
...
<xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
...
</xwss:SecurityConfiguration>
如果用户名令牌不存在,XwsSecurityInterceptor 将向发送方返回一个 SOAP 故障。如果存在,它将向注册的处理程序触发一个带有 PlainTextPasswordRequest 的 PasswordValidationCallback。在 Spring-WS 中,有三个类处理此特定回调。
最简单的密码验证处理程序是 SimplePasswordValidationCallbackHandler。此处理程序根据内存中的 Properties 对象验证密码,您可以使用 users 属性指定该对象,如下所示
<bean id="passwordValidationHandler"
class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="Bert">Ernie</prop>
</props>
</property>
</bean>
在这种情况下,我们只允许用户“Bert”使用密码“Ernie”登录。
SpringPlainTextPasswordValidationCallbackHandler 使用 Spring Security 来认证用户。本文档不涉及 Spring Security 的详细描述,但可以肯定地说它是一个功能完备的安全框架。您可以在 Spring Security 参考文档 中阅读更多相关信息。
SpringPlainTextPasswordValidationCallbackHandler 需要一个 AuthenticationManager 才能运行。它使用此管理器针对其创建的 UsernamePasswordAuthenticationToken 进行身份验证。如果身份验证成功,令牌将存储在 SecurityContextHolder 中。您可以使用 authenticationManager 属性设置身份验证管理器
<beans>
<bean id="springSecurityHandler"
class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
<property name="providers">
<bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
...
</beans>
JaasPlainTextPasswordValidationCallbackHandler 基于标准 Java 身份验证和授权服务 。本文档超出了提供 JAAS 完整介绍的范围,但有一个 不错的教程 可用。
JaasPlainTextPasswordValidationCallbackHandler 仅需要 loginContextName 即可运行。它使用此名称创建一个新的 JAAS LoginContext,并使用 SOAP 消息中提供的用户名和密码处理标准 JAAS NameCallback 和 PasswordCallback。这意味着此回调处理程序与任何在 login() 阶段触发这些回调的 JAAS LoginModule 集成,这是标准行为。
您可以按如下方式连接 JaasPlainTextPasswordValidationCallbackHandler
<bean id="jaasValidationHandler"
class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
<property name="loginContextName" value="MyLoginModule" />
</bean>
在这种情况下,回调处理程序使用名为“MyLoginModule”的 LoginContext。此模块应在您的 jaas.config 文件中定义,如上述教程中所述。
使用密码摘要时,SOAP 消息也包含一个 UsernameToken 元素,其中包含一个 Username 元素和一个 Password 元素。不同之处在于密码不是以纯文本发送,而是以摘要形式发送。接收方将此摘要与它从用户已知密码计算出的摘要进行比较,如果它们相同,则用户通过身份验证。它可以与 HTTP 服务器提供的摘要身份验证进行比较。
为了要求每条传入消息都包含一个带有密码摘要的 UsernameToken 元素,安全策略文件应包含一个 RequireUsernameToken 元素,其中 passwordDigestRequired 属性设置为 true。此外,nonceRequired 应设置为 true:您可以在 此处 找到可能的子元素参考。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
...
<xwss:RequireUsernameToken passwordDigestRequired="true" nonceRequired="true"/>
...
</xwss:SecurityConfiguration>
如果用户名令牌不存在,XwsSecurityInterceptor 将向发送方返回一个 SOAP 故障。如果存在,它将向注册的处理程序触发一个带有 DigestPasswordRequest 的 PasswordValidationCallback。在 Spring-WS 中,有两个类处理此特定回调。
SimplePasswordValidationCallbackHandler 可以处理纯文本密码和密码摘要。它在第 7.2.2.1.1 节,“SimplePasswordValidationCallbackHandler”中进行了描述。
SpringDigestPasswordValidationCallbackHandler 需要一个 Spring Security UserDetailService 才能运行。它使用此服务检索令牌中指定用户的密码。然后将此详细信息对象中包含的密码摘要与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且 UsernamePasswordAuthenticationToken 将存储在 SecurityContextHolder 中。您可以使用 userDetailsService 设置服务。此外,您可以设置 userCache 属性,以缓存加载的用户详细信息。
<beans>
<bean class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
<bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
...
</beans>
一种更安全的身份验证方式是使用 X509 证书。在此场景中,SOAP 消息包含一个 BinarySecurityToken,其中包含 X509 证书的 Base 64 编码版本。接收方使用该证书进行身份验证。消息中存储的证书也用于签名消息(参见第 7.2.3.1 节,“验证签名”)。
为确保所有传入的 SOAP 消息都带有 BinarySecurityToken,安全策略文件应包含一个 RequireSignature 元素。此元素还可以携带其他元素,这些元素将在第 7.2.3.1 节,“验证签名”中介绍。您可以在 此处 找到可能的子元素参考。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
...
<xwss:RequireSignature requireTimestamp="false">
...
</xwss:SecurityConfiguration>
当收到不带证书的消息时,XwsSecurityInterceptor 将向发送方返回一个 SOAP 故障。如果存在,它将触发一个 CertificateValidationCallback。Spring-WS 中有三个处理程序用于身份验证目的处理此回调。
在大多数情况下,证书身份验证之前应进行证书验证,因为您只希望对有效证书进行身份验证。无效证书(例如已过期的证书或不在您的受信任证书存储中的证书)应被忽略。
用 Spring-WS 的术语来说,这意味着 SpringCertificateValidationCallbackHandler 或 JaasCertificateValidationCallbackHandler 之前应是 KeyStoreCallbackHandler。这可以通过在 XwsSecurityInterceptor 的配置中设置 callbackHandlers 属性的顺序来实现。
<bean id="wsSecurityInterceptor"
class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="keyStoreHandler"/>
<ref bean="springSecurityHandler"/>
</list>
</property>
</bean>
通过这种设置,拦截器将首先使用密钥库确定消息中的证书是否有效,然后对其进行身份验证。
KeyStoreCallbackHandler 使用标准 Java 密钥库来验证证书。此证书验证过程包括以下步骤
首先,处理程序将检查证书是否在私有 keyStore 中。如果存在,则有效。
如果证书不在私钥库中,处理程序将检查当前日期和时间是否在证书中给出的有效期内。如果不在,则证书无效;如果在,它将继续执行最后一步。
最后,为证书创建认证路径。这基本上意味着处理程序将确定证书是否由 trustStore 中的任何证书颁发机构颁发。如果成功构建了认证路径,则证书有效。否则,证书无效。
要将 KeyStoreCallbackHandler 用于证书验证目的,您最有可能只设置 trustStore 属性
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="trustStore" ref="trustStore"/>
</bean>
<bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:truststore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>使用此设置,要验证的证书必须在信任存储本身中,或者信任存储必须包含颁发该证书的证书颁发机构。
SpringCertificateValidationCallbackHandler 需要一个 Spring Security AuthenticationManager 才能运行。它使用此管理器来对它创建的 X509AuthenticationToken 进行身份验证。配置的身份验证管理器应提供一个可以处理此令牌的提供程序(通常是 X509AuthenticationProvider 的实例)。如果身份验证成功,令牌将存储在 SecurityContextHolder 中。您可以使用 authenticationManager 属性设置身份验证管理器
<beans>
<bean id="springSecurityCertificateHandler"
class="org.springframework.ws.soap.security.xwss.callback.SpringCertificateValidationCallbackHandler">
<property name="authenticationManager" ref="authenticationManager"/>
</bean>
<bean id="authenticationManager"
class="org.springframework.security.providers.ProviderManager">
<property name="providers">
<bean class="org.springframework.ws.soap.security.x509.X509AuthenticationProvider">
<property name="x509AuthoritiesPopulator">
<bean class="org.springframework.ws.soap.security.x509.populator.DaoX509AuthoritiesPopulator">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
...
</beans>
在这种情况下,我们使用自定义用户详细信息服务根据证书获取身份验证详细信息。有关基于 X509 证书进行身份验证的更多信息,请参阅 Spring Security 参考文档 。
JaasCertificateValidationCallbackHandler 需要 loginContextName 才能运行。它使用此名称和证书的 X500Principal 创建一个新的 JAAS LoginContext。这意味着此回调处理程序与任何处理 X500 主体的 JAAS LoginModule 集成。
您可以按如下方式连接 JaasCertificateValidationCallbackHandler
<bean id="jaasValidationHandler"
class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasCertificateValidationCallbackHandler">
<property name="loginContextName">MyLoginModule</property>
</bean>
在这种情况下,回调处理程序使用名为“MyLoginModule”的 LoginContext。此模块应在您的 jaas.config 文件中定义,并且应能够针对 X500 主体进行身份验证。
消息的数字签名是基于文档和签名者私钥的信息。WS-Security 中与签名相关的任务主要有两个:验证签名和签名消息。
就像基于证书的认证一样,签名消息包含一个 BinarySecurityToken,其中包含用于签名消息的证书。此外,它还包含一个 SignedInfo 块,指示消息的哪个部分已签名。
为确保所有传入的 SOAP 消息都带有 BinarySecurityToken,安全策略文件应包含一个 RequireSignature 元素。它还可以包含一个 SignatureTarget 元素,用于指定预期签名的目标消息部分,以及各种其他子元素。您还可以定义要使用的私钥别名、是使用对称密钥还是私钥,以及许多其他属性。您可以在 此处 找到可能的子元素参考。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:RequireSignature requireTimestamp="false"/>
</xwss:SecurityConfiguration>
如果签名不存在,XwsSecurityInterceptor 将向发送方返回一个 SOAP 故障。如果存在,它将向注册的处理程序触发一个 SignatureVerificationKeyCallback。在 Spring-WS 中,有一个类处理此特定回调:KeyStoreCallbackHandler。
如第 7.2.1.3 节,“KeyStoreCallbackHandler”中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 处理各种加密回调,包括签名验证。对于签名验证,处理程序使用 trustStore 属性
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="trustStore" ref="trustStore"/>
</bean>
<bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-truststore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>
签名消息时,XwsSecurityInterceptor 将 BinarySecurityToken 和一个 SignedInfo 块添加到消息中,该块指示消息的哪个部分已签名。
为了签名所有传出的 SOAP 消息,安全策略文件应包含一个 Sign 元素。它还可以包含一个 SignatureTarget 元素,用于指定预期签名的目标消息部分,以及各种其他子元素。您还可以定义要使用的私钥别名、是使用对称密钥还是私钥,以及许多其他属性。您可以在 此处 找到可能的子元素参考。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config"> <xwss:Sign includeTimestamp="false" /> </xwss:SecurityConfiguration>
XwsSecurityInterceptor 将向注册的处理程序触发一个 SignatureKeyCallback。在 Spring-WS 中,有一个类处理此特定回调:KeyStoreCallbackHandler。
如第 7.2.1.3 节,“KeyStoreCallbackHandler”中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 处理各种加密回调,包括签名消息。对于添加签名,处理程序使用 keyStore 属性。此外,您必须设置 privateKeyPassword 属性以解锁用于签名的私钥。
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore"/>
<property name="privateKeyPassword" value="changeit"/>
</bean>
<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:keystore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>
当加密时,消息被转换为只能使用适当密钥读取的形式。消息可以解密以揭示原始的、可读的消息。
要解密传入的 SOAP 消息,安全策略文件应包含一个 RequireEncryption 元素。此元素还可以包含一个 EncryptionTarget 元素,指示消息的哪一部分应加密,以及一个 SymmetricKey 指示应使用共享密钥而不是常规私钥来解密消息。您可以在 此处 阅读其他元素的描述。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:RequireEncryption />
</xwss:SecurityConfiguration>
如果传入消息未加密,XwsSecurityInterceptor 将向发送方返回一个 SOAP 故障。如果已加密,它将向注册的处理程序触发一个 DecryptionKeyCallback。在 Spring-WS 中,有一个类处理此特定回调:KeyStoreCallbackHandler。
如第 7.2.1.3 节,“KeyStoreCallbackHandler”中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 处理各种加密回调,包括解密。对于解密,处理程序使用 keyStore 属性。此外,您必须设置 privateKeyPassword 属性以解锁用于解密的私钥。对于基于对称密钥的解密,它将使用 symmetricStore。
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore"/>
<property name="privateKeyPassword" value="changeit"/>
</bean>
<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:keystore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>
要加密传出的 SOAP 消息,安全策略文件应包含一个 Encrypt 元素。此元素还可以携带一个 EncryptionTarget 元素,指示消息的哪个部分应加密,以及一个 SymmetricKey 指示应使用共享密钥而不是常规公钥来加密消息。您可以在 此处 阅读其他元素的描述。
<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
<xwss:Encrypt />
</xwss:SecurityConfiguration>
XwsSecurityInterceptor 将向注册的处理程序触发 EncryptionKeyCallback 以检索加密信息。在 Spring-WS 中,有一个类处理此特定回调:KeyStoreCallbackHandler。
如第 7.2.1.3 节,“KeyStoreCallbackHandler”中所述,KeyStoreCallbackHandler 使用 java.security.KeyStore 处理各种加密回调,包括加密。对于基于公钥的加密,处理程序使用 trustStore 属性。对于基于对称密钥的加密,它将使用 symmetricStore。
<beans>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="trustStore" ref="trustStore"/>
</bean>
<bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:truststore.jks"/>
<property name="password" value="changeit"/>
</bean>
</beans>
当安全或验证操作失败时,XwsSecurityInterceptor 将分别抛出 WsSecuritySecurementException 或 WsSecurityValidationException。这些异常绕过标准异常处理机制,但在拦截器本身中处理。
WsSecuritySecurementException 异常在 XwsSecurityInterceptor 的 handleSecurementException 方法中处理。默认情况下,此方法将简单地记录一个错误,并停止进一步处理消息。
类似地,WsSecurityValidationException 异常在 XwsSecurityInterceptor 的 handleValidationException 方法中处理。默认情况下,此方法将创建 SOAP 1.1 客户端或 SOAP 1.2 发送方故障,并将其作为响应发送回去。
handleSecurementException 和 handleValidationException 都是受保护的方法,您可以重写它们以更改其默认行为。
Wss4jSecurityInterceptor 是一个 EndpointInterceptor(参见第 5.5.2 节,“拦截请求 - EndpointInterceptor 接口”),它基于 Apache 的 WSS4J。
WSS4J 实现了以下标准
OASIS Web 服务安全:SOAP 消息安全 1.0 标准 200401,2004 年 3 月
用户名令牌配置文件 V1.0
X.509 令牌配置文件 V1.0
此拦截器支持由 AxiomSoapMessageFactory 和 SaajSoapMessageFactory 创建的消息。
WSS4J 不使用外部配置文件;拦截器完全由属性配置。此拦截器执行的验证和安全操作分别通过 validationActions 和 securementActions 属性指定。操作作为空格分隔的字符串传递。这是一个配置示例
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="UsernameToken Encrypt"/>
...
<property name="securementActions" value="Encrypt"/>
...
</bean>
验证操作包括
| 验证操作 | 描述 |
|---|---|
用户名令牌
| 验证用户名令牌 |
时间戳
| 验证时间戳 |
加密
| 解密消息 |
签名
| 验证签名 |
无安全
| 未执行任何操作 |
安全措施是
| 安全操作 | 描述 |
|---|---|
用户名令牌
| 添加用户名令牌 |
用户名令牌签名
| 添加用户名令牌和签名用户名令牌密钥 |
时间戳
| 添加时间戳 |
加密
| 加密响应 |
签名
| 签署响应 |
无安全
| 未执行任何操作 |
操作的顺序很重要,并由拦截器强制执行。如果传入 SOAP 消息的安全操作的执行顺序与 validationActions 指定的顺序不同,拦截器将拒绝该消息。
对于需要与密钥库或证书处理(签名、加密和解密操作)交互的加密操作,WSS4J 需要一个 org.apache.ws.security.components.crypto.Crypto 实例。
Crypto 实例可以从 WSS4J 的 CryptoFactory 获取,或者更方便地使用 Spring-WS 的 CryptoFactoryBean 获取。
Spring-WS 提供了一个方便的工厂 bean,CryptoFactoryBean,它通过强类型属性(首选)或通过 Properties 对象构造和配置 Crypto 实例。
默认情况下,CryptoFactoryBean 返回 org.apache.ws.security.components.crypto.Merlin 的实例。这可以通过设置 cryptoProvider 属性(或其等效的 org.apache.ws.security.crypto.provider 字符串属性)来更改。
这是一个简单的配置示例
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="mypassword"/>
<property name="keyStoreLocation" value="file:/path_to_keystore/keystore.jks"/>
</bean>
Spring-WS 提供了一组回调处理程序以与 Spring Security 集成。此外,还提供了一个简单的回调处理程序 SimplePasswordValidationCallbackHandler,用于使用内存中的 Properties 对象配置用户和密码。
回调处理程序通过 Wss4jSecurityInterceptor 的 validationCallbackHandler 属性进行配置。
SimplePasswordValidationCallbackHandler 根据内存中的 Properties 对象验证纯文本和摘要用户名令牌。其配置如下
<bean id="callbackHandler"
class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
<property name="users">
<props>
<prop key="Bert">Ernie</prop>
</props>
</property>
</bean>
SpringSecurityPasswordValidationCallbackHandler 使用 Spring Security UserDetailService 来验证纯文本和摘要密码。它使用此服务检索令牌中指定用户的密码(的摘要)。然后将此详细信息对象中包含的密码(的摘要)与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且 UsernamePasswordAuthenticationToken 将存储在 SecurityContextHolder 中。您可以使用 userDetailsService 设置服务。此外,您可以设置 userCache 属性,以缓存加载的用户详细信息。
<beans>
<bean class="org.springframework.ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
<bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
...
</beans>
将用户名令牌添加到传出消息中非常简单,只需将 UsernameToken 添加到 Wss4jSecurityInterceptor 的 securementActions 属性中,并指定 securementUsername 和 securementPassword 即可。
密码类型可以通过 securementPasswordType 属性设置。可能的值为用于纯文本密码的 PasswordText 或用于摘要密码的 PasswordDigest,后者是默认值。
以下示例生成带有摘要密码的用户名令牌
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="UsernameToken"/>
<property name="securementUsername" value="Ernie"/>
<property name="securementPassword" value="Bert"/>
</bean>
如果选择纯文本密码类型,可以指示拦截器使用 securementUsernameTokenElements 属性添加 Nonce 和/或 Created 元素。该值必须是包含所需元素名称的列表,以空格分隔(区分大小写)。
下一个示例生成一个带有纯文本密码、Nonce 和 Created 元素的用户名令牌
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="UsernameToken"/>
<property name="securementUsername" value="Ernie"/>
<property name="securementPassword" value="Bert"/>
<property name="securementPasswordType" value="PasswordText"/>
<property name="securementUsernameTokenElements" value="Nonce Created"/>
</bean>
由于证书认证类似于数字签名,WSS4J 将其作为签名验证和安全措施的一部分来处理。具体来说,必须将 securementSignatureKeyIdentifier 属性设置为 DirectReference,以便指示 WSS4J 生成一个包含 X509 证书的 BinarySecurityToken 元素,并将其包含在传出消息中。证书的名称和密码分别通过 securementUsername 和 securementPassword 属性传递。请参见以下示例
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Signature"/>
<property name="securementSignatureKeyIdentifier" value="DirectReference"/>
<property name="securementUsername" value="mycert"/>
<property name="securementPassword" value="certpass"/>
<property name="securementSignatureCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="classpath:/keystore.jks"/>
</bean>
</property>
</bean>
对于证书验证,适用常规签名验证
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Signature"/>
<property name="validationSignatureCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="classpath:/keystore.jks"/>
</bean>
</property>
</bean>
在验证结束时,拦截器将通过委托给默认的 WSS4J 实现自动验证证书的有效性。如果需要,可以通过重新定义 verifyCertificateTrust 方法来更改此行为。
有关更多详细信息,请参阅第 7.3.5 节,“数字签名”。
本节描述了 Wss4jSecurityInterceptor 中可用的各种时间戳选项。
要验证时间戳,请将 Timestamp 添加到 validationActions 属性中。可以通过将 timestampStrict 设置为 true 并通过 timeToLive 属性指定服务器端生存时间(默认为 300)来覆盖 SOAP 消息发起者指定的时间戳语义 [3] 。
在以下示例中,拦截器将时间戳有效期窗口限制为 10 秒,拒绝在该窗口之外的任何有效时间戳令牌
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Timestamp"/>
<property name="timestampStrict" value="true"/>
<property name="timeToLive" value="10"/>
</bean>
将 Timestamp 添加到 securementActions 属性会在传出消息中生成时间戳头。 timestampPrecisionInMilliseconds 属性指定生成的时间戳精度是否为毫秒。默认值为 true。
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Timestamp"/>
<property name="timestampPrecisionInMilliseconds" value="true"/>
</bean>
本节描述了 Wss4jSecurityInterceptor 中可用的各种签名选项。
要指示 Wss4jSecurityInterceptor,validationActions 必须包含 Signature 操作。此外,validationSignatureCrypto 属性必须指向包含发起者公共证书的密钥库
<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Signature"/>
<property name="validationSignatureCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="classpath:/keystore.jks"/>
</bean>
</property>
</bean>
通过将 Signature 操作添加到 securementActions 中,可以启用对传出消息的签名。要使用的私钥的别名和密码分别由 securementUsername 和 securementPassword 属性指定。securementSignatureCrypto 必须指向包含私钥的密钥库
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Signature"/>
<property name="securementUsername" value="mykey"/>
<property name="securementPassword" value="123456"/>
<property name="securementSignatureCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="classpath:/keystore.jks"/>
</bean>
</property>
</bean>
此外,可以通过 securementSignatureAlgorithm 定义签名算法。
要使用的密钥标识符类型可以通过 securementSignatureKeyIdentifier 属性进行自定义。对于签名,只有 IssuerSerial 和 DirectReference 有效。
securementSignatureParts 属性控制消息的哪些部分应被签名。此属性的值是一个以分号分隔的元素名称列表,用于标识要签名的元素。签名部分的一般形式是 {}{namespace}Element [4] 。默认行为是签名 SOAP 正文。
例如,以下是如何签署 Spring Web Services 回显示例中的 echoResponse 元素
<property name="securementSignatureParts"
value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
WS Security 规范定义了几种格式来传输签名令牌(证书)或对这些令牌的引用。因此,纯元素名称 Token 对令牌进行签名,并处理不同的格式。要对 SOAP 正文和签名令牌进行签名,securementSignatureParts 的值必须包含
<property name="securementSignatureParts">
<value>
{}{http://schemas.xmlsoap.org/soap/envelope/}Body;
Token
</value>
</property>
要指定没有命名空间的元素,请使用字符串 Null 作为命名空间名称(区分大小写)。
如果在请求中没有其他局部名称为 Body 的元素,则 SOAP 命名空间标识符可以为空({})。
通过将 enableSignatureConfirmation 设置为 true 启用签名确认。请注意,签名确认操作涵盖请求和响应。这意味着 secureResponse 和 validateRequest 必须设置为 true(这是默认值),即使没有相应的安全操作。
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Signature"/>
<property name="enableSignatureConfirmation" value="true"/>
<property name="validationSignatureCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="file:/keystore.jks"/>
</bean>
</property>
</bean>
本节描述了 Wss4jSecurityInterceptor 中可用的各种加密和解密选项。
解密传入的 SOAP 消息需要将 Encrypt 操作添加到 validationActions 属性中。其余配置取决于消息中出现的密钥信息 [5] 。
要解密包含嵌入式加密对称密钥(xenc:EncryptedKey 元素)的消息,validationDecryptionCrypto 需要指向包含解密私钥的密钥库。此外,validationCallbackHandler 必须注入一个 org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler,并指定密钥密码
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Encrypt"/>
<property name="validationDecryptionCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="classpath:/keystore.jks"/>
</bean>
</property>
<property name="validationCallbackHandler">
<bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
<property name="privateKeyPassword" value="mykeypass"/>
</bean>
</property>
</bean>
为了支持解密具有嵌入式密钥名称(ds:KeyName 元素)的消息,请配置一个指向包含对称密钥的密钥库的 KeyStoreCallbackHandler。属性 symmetricKeyPassword 指示密钥密码,密钥名称是 ds:KeyName 元素指定的名称
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="validationActions" value="Encrypt"/>
<property name="validationCallbackHandler">
<bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
<property name="keyStore">
<bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="classpath:keystore.jks"/>
<property name="type" value="JCEKS"/>
<property name="password" value="123456"/>
</bean>
</property>
<property name="symmetricKeyPassword" value="mykeypass"/>
</bean>
</property>
</bean>
将 Encrypt 添加到 securementActions 可启用对传出消息的加密。用于加密的证书别名通过 securementEncryptionUser 属性设置。包含证书的密钥库通过 securementEncryptionCrypto 属性访问。由于加密依赖于公共证书,因此无需传递密码。
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Encrypt"/>
<property name="securementEncryptionUser" value="mycert"/>
<property name="securementEncryptionCrypto">
<bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="123456"/>
<property name="keyStoreLocation" value="file:/keystore.jks"/>
</bean>
</property>
</bean>
加密可以通过多种方式自定义:要使用的密钥标识符类型由 securementEncryptionKeyIdentifier 定义。可能的值为 IssuerSerial、X509KeyIdentifier、DirectReference、Thumbprint、SKIKeyIdentifier 或 EmbeddedKeyName。
如果选择了 EmbeddedKeyName 类型,您需要指定用于加密的密钥。密钥的别名通过 securementEncryptionUser 属性设置,与其他密钥标识符类型相同。但是,WSS4J 需要一个回调处理程序来获取密钥。因此,必须为 securementCallbackHandler 提供一个指向相应密钥库的 KeyStoreCallbackHandler。默认情况下,结果 WS-Security 头部中的 ds:KeyName 元素采用 securementEncryptionUser 属性的值。要指示不同的名称,请使用所需值设置 securementEncryptionEmbeddedKeyName。在下一个示例中,传出消息将使用别名为 secretKey 的密钥进行加密,而 myKey 将出现在 ds:KeyName 元素中
<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Encrypt"/>
<property name="securementEncryptionKeyIdentifier" value="EmbeddedKeyName"/>
<property name="securementEncryptionUser" value="secretKey"/>
<property name="securementEncryptionEmbeddedKeyName" value="myKey"/>
<property name="securementCallbackHandler">
<bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
<property name="symmetricKeyPassword" value="keypass"/>
<property name="keyStore">
<bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="file:/keystore.jks"/>
<property name="type" value="jceks"/>
<property name="password" value="123456"/>
</bean>
</property>
</bean>
</property>
</bean>
securementEncryptionKeyTransportAlgorithm 属性定义用于加密生成的对称密钥的算法。支持的值为 http://www.w3.org/2001/04/xmlenc#rsa-1_5(默认值)和 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p。
要使用的对称加密算法可以通过 securementEncryptionSymAlgorithm 属性设置。支持的值为 http://www.w3.org/2001/04/xmlenc#aes128-cbc(默认值)、http://www.w3.org/2001/04/xmlenc#tripledes-cbc、http://www.w3.org/2001/04/xmlenc#aes256-cbc、http://www.w3.org/2001/04/xmlenc#aes192-cbc。
最后,securementEncryptionParts 属性定义消息的哪些部分将被加密。此属性的值是一个以分号分隔的元素名称列表,用于标识要加密的元素。每个元素名称前面可以加上加密模式指定符和命名空间标识符,每个都包含在一对花括号内。加密模式指定符可以是 {Content} 或 {Element} [6] 。以下示例标识了来自 echo 示例的 echoResponse
<property name="securementEncryptionParts"
value="{Content}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
请注意,元素名称、命名空间标识符和加密修饰符区分大小写。可以省略加密修饰符和命名空间标识符。在这种情况下,加密模式默认为 Content,命名空间设置为 SOAP 命名空间。
要指定没有命名空间的元素,请使用值 Null 作为命名空间名称(区分大小写)。如果未指定列表,则处理程序默认以 Content 模式加密 SOAP 正文。
Wss4jSecurityInterceptor 的异常处理与 XwsSecurityInterceptor 的异常处理相同。有关更多信息,请参阅第 7.2.5 节,“安全异常处理”。