第 7 章. 使用 Spring-WS 保护你的 Web 服务

7.1. 引言

本章解释了如何为你的 Web 服务添加 WS-Security 方面。我们将重点关注 WS-Security 的三个不同领域,即

认证。  这个过程用于确定主体是否与其声称的身份一致。在此上下文中,“主体”通常指用户、设备或应用程序中可以执行某种操作的其他系统。

数字签名。  消息的数字签名是基于文档和签名者私钥的一段信息。它通过使用哈希函数和私有签名函数(使用签名者的私钥加密)创建。

加密和解密。  加密是将数据转换为一种形式,使其在没有适当密钥的情况下无法读取。它主要用于向未经授权的人隐藏信息。解密是加密的逆过程;它是将加密数据转换回可读形式的过程。

所有这三个领域都通过 XwsSecurityInterceptorWss4jSecurityInterceptor 实现,我们将在第 7.2 节,“ XwsSecurityInterceptor第 7.3 节,“ Wss4jSecurityInterceptor中分别描述它们。

注意

注意 WS-Security(尤其是加密和签名)需要大量内存,并且会降低性能。如果性能对你很重要,你可能需要考虑不使用 WS-Security,或者只使用基于 HTTP 的安全机制。

7.2. XwsSecurityInterceptor

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 证书对传出消息进行签名。以下章节将说明针对哪种安全问题使用哪种回调处理器。你可以使用 callbackHandlercallbackHandlers 属性设置回调处理器。

下面是一个示例,展示如何配置 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 文件进行配置。它使用了文件中进一步定义的两个回调处理器。

7.2.1. 密钥库

对于大多数加密操作,你将使用标准的 java.security.KeyStore 对象。这些操作包括证书验证、消息签名、签名验证和加密,但不包括用户名和时间戳验证。本节旨在为你提供一些关于密钥库的背景知识,以及你可以用来在密钥库文件中存储密钥和证书的 Java 工具。这些信息主要与 Spring-WS 无关,而是与 Java 的通用加密功能相关。

java.security.KeyStore 类表示加密密钥和证书的存储设施。它可以包含三种不同类型的元素

私钥。  这些密钥用于自身认证。私钥伴随有对应公钥的证书链。在 WS-Security 领域,这对应于消息签名和消息解密。

对称密钥。  对称(或秘密)密钥也用于消息加密和解密。不同之处在于,双方(发送方和接收方)共享同一个秘密密钥。

信任的证书。  这些 X509 证书之所以被称为信任的证书,是因为密钥库所有者信任证书中的公钥确实属于证书所有者。在 WS-Security 中,这些证书用于证书验证、签名验证和加密。

7.2.1.1. KeyTool

你的 Java 虚拟机随附了 keytool 程序,这是一个密钥和证书管理工具。你可以使用此工具创建新的密钥库,向其中添加新的私钥和证书等。本文档的范围不包括提供 keytool 命令的完整参考,但你可以在 此处 找到参考,或通过在命令行输入命令 keytool -help

7.2.1.2. KeyStoreFactoryBean

为了使用 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>

注意

如果你没有指定位置属性,将创建一个新的空密钥库,这很可能不是你想要的。

7.2.1.3. KeyStoreCallbackHandler

要在 XwsSecurityInterceptor 中使用密钥库,你需要定义一个 KeyStoreCallbackHandler。此回调处理程序有三个类型为密钥库的属性:(keyStoretrustStoresymmetricStore)。处理程序使用的确切密钥库取决于要执行的加密操作。对于私钥操作,使用 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 可以在何处使用,以及针对特定加密操作应设置哪些属性。

7.2.2. 认证

如引言所述,认证是确定主体是否与其声称身份一致的任务。在 WS-Security 中,认证可以有两种形式:使用用户名和密码令牌(使用明文密码或密码摘要),或使用 X509 证书。

7.2.2.1. 明文用户名认证

最简单的用户名认证形式使用明文密码。在这种情况下,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。如果存在,它将向注册的处理器触发一个带有 PlainTextPasswordRequestPasswordValidationCallback。在 Spring-WS 中,有三个类处理这种特定的回调。

7.2.2.1.1. SimplePasswordValidationCallbackHandler

最简单的密码验证处理器是 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”登录。

7.2.2.1.2. SpringPlainTextPasswordValidationCallbackHandler

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>
7.2.2.1.3. JaasPlainTextPasswordValidationCallbackHandler

JaasPlainTextPasswordValidationCallbackHandler 基于标准的 Java Authentication and Authorization Service 。提供 JAAS 的完整介绍超出了本文档的范围,但有一个 不错的教程 可用。

JaasPlainTextPasswordValidationCallbackHandler 只需要一个 loginContextName 即可运行。它使用此名称创建一个新的 JAAS LoginContext,并使用 SOAP 消息中提供的用户名和密码处理标准的 JAAS NameCallbackPasswordCallback。这意味着此回调处理器可以与任何在 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 文件中定义。

7.2.2.2. 摘要用户名认证

使用密码摘要时,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。如果存在,它将向注册的处理器触发一个带有 DigestPasswordRequestPasswordValidationCallback。在 Spring-WS 中,有两个类处理这种特定的回调。

7.2.2.2.1. SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler 可以处理明文密码和密码摘要。它在第 7.2.2.1.1 节,“SimplePasswordValidationCallbackHandler”中有所描述。

7.2.2.2.2. SpringDigestPasswordValidationCallbackHandler

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>

7.2.2.3. 证书认证

一种更安全的认证方式是使用 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 中,这意味着 SpringCertificateValidationCallbackHandlerJaasCertificateValidationCallbackHandler 应先于 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.2.3.1. KeyStoreCallbackHandler

第 7.2.1.3 节,“KeyStoreCallbackHandler”所述,KeyStoreCallbackHandler 使用标准的 Java 密钥库来验证证书。此证书验证过程包括以下步骤

  1. 首先,处理程序将检查证书是否在私有的 keyStore 中。如果是,则有效。

  2. 如果证书不在私有密钥库中,处理程序将检查当前日期和时间是否在证书中给定的有效期内。如果不在,则证书无效;如果在,则继续执行最后一步。

  3. 最后,为证书创建认证路径。这基本上意味着处理程序将确定证书是否由 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 必须包含颁发该证书的证书颁发机构。

7.2.2.3.2. SpringCertificateValidationCallbackHandler

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 参考文档

7.2.2.3.3. JaasCertificateValidationCallbackHandler

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 进行认证。

7.2.3. 数字签名

消息的数字签名是基于文档和签名者私钥的一段信息。在 WS-Security 中,与签名相关的两个主要任务是:验证签名和对消息进行签名。

7.2.3.1. 验证签名

就像基于证书的认证一样,已签名的消息包含一个 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.3.1.1. 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>

7.2.3.2. 对消息进行签名

对消息进行签名时,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.3.2.1. 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>

7.2.4. 加密和解密

加密时,消息被转换为一种只有拥有适当密钥才能读取的形式。消息可以被解密以显示原始的可读消息。

7.2.4.1. 解密

要解密传入的 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.4.1.1. 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>

7.2.4.2. 加密

要加密传出的 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.4.2.1. 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>

7.2.5. 安全异常处理

当安全或验证操作失败时,XwsSecurityInterceptor 将分别抛出 WsSecuritySecurementExceptionWsSecurityValidationException。这些异常绕过标准异常处理机制,但在拦截器自身中进行处理。

WsSecuritySecurementException 异常在 XwsSecurityInterceptorhandleSecurementException 方法中处理。默认情况下,此方法仅记录错误,并停止消息的进一步处理。

类似地,WsSecurityValidationException 异常在 XwsSecurityInterceptorhandleValidationException 方法中处理。默认情况下,此方法将创建 SOAP 1.1 Client 或 SOAP 1.2 Sender Fault,并将其作为响应发送回去。

注意

handleSecurementExceptionhandleValidationException 都是受保护的方法,你可以覆盖它们以更改其默认行为。

7.3. Wss4jSecurityInterceptor

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

此拦截器支持由 AxiomSoapMessageFactorySaajSoapMessageFactory 创建的消息。

7.3.1. 配置 Wss4jSecurityInterceptor

WSS4J 不使用外部配置文件;拦截器完全通过属性进行配置。此拦截器执行的验证和安全操作分别通过 validationActionssecurementActions 属性指定。操作作为空格分隔的字符串传递。这是一个示例配置

<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 指定的顺序不同,拦截器将拒绝该消息。

7.3.2. 处理数字证书

对于需要与密钥库或证书处理交互的加密操作(签名、加密和解密操作),WSS4J 需要一个 org.apache.ws.security.components.crypto.Crypto 的实例。

Crypto 实例可以从 WSS4J 的 CryptoFactory 获取,或者更方便地使用 Spring-WS 的 CryptoFactoryBean 获取。

7.3.2.1. 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>
				

7.3.3. 认证

7.3.3.1. 验证用户名令牌

Spring-WS 提供了一套回调处理器,用于与 Spring Security 集成。此外,还提供了一个简单的回调处理器 SimplePasswordValidationCallbackHandler,用于使用内存中的 Properties 对象配置用户和密码。

回调处理器通过 Wss4jSecurityInterceptorvalidationCallbackHandler 属性进行配置。

7.3.3.1.1. SimplePasswordValidationCallbackHandler

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>
7.3.3.1.2. SpringSecurityPasswordValidationCallbackHandler

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>
					

7.3.3.2. 添加用户名令牌

将用户名令牌添加到传出消息中就像将 UsernameToken 添加到 Wss4jSecurityInterceptorsecurementActions 属性中,并指定 securementUsernamesecurementPassword 一样简单。

密码类型可以通过 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 元素。该值必须是一个列表,包含以空格分隔的所需元素名称(区分大小写)。

下面的示例生成带有纯文本密码、NonceCreated 元素的用户名令牌

<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>

7.3.3.3. 证书认证

由于证书认证类似于数字签名,WSS4J 将其作为签名验证和保护的一部分来处理。具体来说,securementSignatureKeyIdentifier 属性必须设置为 DirectReference,以便指示 WSS4J 生成包含 X509 证书的 BinarySecurityToken 元素并将其包含在传出消息中。证书的名称和密码分别通过 securementUsernamesecurementPassword 属性传递。请参见下面的示例

<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 节,“数字签名”

7.3.4. 安全时间戳

本节介绍 Wss4jSecurityInterceptor 中可用的各种时间戳选项。

7.3.4.1. 验证时间戳

要验证时间戳,请将 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>
					 

7.3.4.2. 添加时间戳

Timestamp 添加到 securementActions 属性会在传出消息中生成时间戳头部。timestampPrecisionInMilliseconds 属性指定生成的时间戳精度是否为毫秒。默认值为 true

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Timestamp"/>
    <property name="timestampPrecisionInMilliseconds" value="true"/>
</bean>
					

7.3.5. 数字签名

本节介绍 Wss4jSecurityInterceptor 中可用的各种签名选项。

7.3.5.1. 验证签名

为了指示 Wss4jSecurityInterceptorvalidationActions 必须包含 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>

7.3.5.2. 签署消息

通过将 Signature 操作添加到 securementActions 来启用对传出消息的签名。要使用的私钥别名和密码分别由 securementUsernamesecurementPassword 属性指定。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 属性进行自定义。对于签名,只有 IssuerSerialDirectReference 有效。

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 命名空间标识符可以为空 ({})。

7.3.5.3. 签名确认

通过将 enableSignatureConfirmation 设置为 true 来启用签名确认。请注意,签名确认操作跨越请求和响应。这意味着即使没有相应的安全操作,secureResponsevalidateRequest 也必须设置为 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>

7.3.6. 加密和解密

本节介绍 Wss4jSecurityInterceptor 中可用的各种加密和解密选项。

7.3.6.1. 解密

解密传入的 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 元素)的消息,请配置一个指向包含对称密钥的密钥库的 KeyStoreCallbackHandlersymmetricKeyPassword 属性指示密钥的密码,密钥名称由 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>

7.3.6.2. 加密

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 定义。可能的值包括IssuerSerialX509KeyIdentifierDirectReferenceThumbprintSKIKeyIdentifierEmbeddedKeyName

如果选择 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_5http://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-cbchttp://www.w3.org/2001/04/xmlenc#aes256-cbchttp://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。

7.3.7. 安全异常处理

Wss4jSecurityInterceptor 的异常处理与 XwsSecurityInterceptor 的异常处理相同。有关更多信息,请参阅 第 7.2.5 节,“安全异常处理”



[3] 无论 timeToLive 的值是多少,拦截器都会始终拒绝已过期的时间戳。

[4] 第一个空括号仅用于加密部分。

[5] 这是因为 WSS4J 只需要一个 Crypto 来处理加密密钥,而嵌入式密钥名称验证则委托给回调处理器。

[6] 请参阅 W3C XML 加密规范了解元素加密和内容加密之间的差异。