本章解释了如何为你的 Web 服务添加 WS-Security 方面。我们将重点关注 WS-Security 的三个不同领域,即
认证。 这个过程用于确定主体是否与其声称的身份一致。在此上下文中,“主体”通常指用户、设备或应用程序中可以执行某种操作的其他系统。
数字签名。 消息的数字签名是基于文档和签名者私钥的一段信息。它通过使用哈希函数和私有签名函数(使用签名者的私钥加密)创建。
加密和解密。 加密是将数据转换为一种形式,使其在没有适当密钥的情况下无法读取。它主要用于向未经授权的人隐藏信息。解密是加密的逆过程;它是将加密数据转换回可读形式的过程。
所有这三个领域都通过 XwsSecurityInterceptor
或 Wss4jSecurityInterceptor
实现,我们将在第 7.2 节,“ XwsSecurityInterceptor
”和第 7.3 节,“ Wss4jSecurityInterceptor
”中分别描述它们。
注意 WS-Security(尤其是加密和签名)需要大量内存,并且会降低性能。如果性能对你很重要,你可能需要考虑不使用 WS-Security,或者只使用基于 HTTP 的安全机制。
XwsSecurityInterceptor
是一个 EndpointInterceptor
(参见第 5.5.2 节,“拦截请求 - EndpointInterceptor
接口”),它基于 SUN 的 XML 和 Web 服务安全包 (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
。如果未设置 keyStore 或 trustStore,回调处理器将使用标准的 Java 机制加载或创建它们。请参阅 KeyStoreCallbackHandler
的 JavaDoc 以了解此机制的工作原理。
例如,如果你想使用 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>
如果你想使用它来解密传入的证书或对传出消息进行签名,你可以使用 keyStore,如下所示
<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 Fault。如果存在,它将向注册的处理器触发一个带有 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 Authentication and Authorization Service 。提供 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 Fault。如果存在,它将向注册的处理器触发一个带有 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 Fault。如果存在,它将触发一个 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>
使用此设置,拦截器将首先使用密钥库确定消息中的证书是否有效,然后对其进行认证。
如第 7.2.1.3 节,“KeyStoreCallbackHandler”所述,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>
使用此设置,要验证的证书必须位于 trustStore 本身中,或者 trustStore 必须包含颁发该证书的证书颁发机构。
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 principal 的 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 principal 进行认证。
消息的数字签名是基于文档和签名者私钥的一段信息。在 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 Fault。如果存在,它将向注册的处理器触发一个 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 Fault。如果已加密,它将向注册的处理器触发一个 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 Client 或 SOAP 1.2 Sender Fault,并将其作为响应发送回去。
handleSecurementException
和 handleValidationException
都是受保护的方法,你可以覆盖它们以更改其默认行为。
Wss4jSecurityInterceptor
是一个 EndpointInterceptor
(参见第 5.5.2 节,“拦截请求 - EndpointInterceptor
接口”),它基于Apache 的 WSS4J。
WSS4J 实现了以下标准
OASIS Web Services Security: SOAP Message Security 1.0 Standard 200401, March 2004
Username Token profile V1.0
X.509 Token Profile 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>
验证操作包括
验证操作 | 描述 |
---|---|
UsernameToken
| 验证用户名令牌 |
Timestamp
| 验证时间戳 |
Encrypt
| 解密消息 |
Signature
| 验证签名 |
NoSecurity
| 不执行任何操作 |
安全操作包括
安全操作 | 描述 |
---|---|
UsernameToken
| 添加用户名令牌 |
UsernameTokenSignature
| 添加用户名令牌和签名用户名令牌秘密密钥 |
Timestamp
| 添加时间戳 |
Encrypt
| 加密响应 |
Signature
| 对响应进行签名 |
NoSecurity
| 不执行任何操作 |
操作的顺序很重要,并由拦截器强制执行。如果传入 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 body。
例如,以下是如何在 Spring Web Services 回显示例中签名 echoResponse
元素的方法
<property name="securementSignatureParts" value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
WS Security 规范定义了几种格式来传输签名令牌(证书)或对这些令牌的引用。因此,简单的元素名称 Token
会签署令牌并处理不同的格式。要签署 SOAP body 和签名令牌,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] 。以下示例标识了回显示例中的 echoResponse
<property name="securementEncryptionParts" value="{Content}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
请注意,元素名称、命名空间标识符和加密修饰符是区分大小写的。加密修饰符和命名空间标识符可以省略。在这种情况下,加密模式默认为 Content
,命名空间设置为 SOAP 命名空间。
要指定没有命名空间的元素,请使用值 Null
作为命名空间名称(区分大小写)。如果未指定列表,处理器默认以 Content
模式加密 SOAP Body。
Wss4jSecurityInterceptor
的异常处理与 XwsSecurityInterceptor
的异常处理相同。有关更多信息,请参阅 第 7.2.5 节,“安全异常处理”。