第 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 服务开发人员包 (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>

注意

如果您未指定 location 属性,则会创建一个新的空密钥库,这很可能不是您想要的。

7.2.1.3. KeyStoreCallbackHandler

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

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 错误。如果存在,它将向注册的处理程序触发带有 PlainTextPasswordRequestPasswordValidationCallback

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 身份验证和授权服务 。本文档不提供对 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 错误。如果存在,它将使用 DigestPasswordRequest 向注册的处理程序触发 PasswordValidationCallback。在 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 错误。如果存在,它将触发 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

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>

使用此设置,要验证的证书必须位于信任库本身中,或者信任库必须包含颁发证书的证书颁发机构。

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 主体的 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 主体进行身份验证。

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 错误。如果存在,它将向注册的处理程序触发 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 错误。如果存在,它将触发一个 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 客户端或 SOAP 1.2 发送方错误,并将其作为响应发送回。

注意

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

7.3.  Wss4jSecurityInterceptor

Wss4jSecurityInterceptor 是一个 EndpointInterceptor(参见5.5.2 节“拦截请求 - EndpointInterceptor 接口”),它基于Apache 的 WSS4J

WSS4J 实现以下标准

  • OASIS Web Services Security:SOAP 消息安全 1.0 标准 200401,2004 年 3 月

  • 用户名令牌配置文件 V1.0

  • X.509 令牌配置文件 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 元素。该值必须是一个列表,其中包含以空格分隔的所需元素的名称(区分大小写)。

以下示例使用明文密码、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>

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 主体。

例如,以下是如何在 Spring Web Services echo 示例中签名 echoResponse 元素。

<property name="securementSignatureParts"
    value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>
    			

WS 安全规范定义了几种格式来传输签名令牌(证书)或对这些令牌的引用。因此,普通元素名称 Token 会对令牌进行签名并处理不同的格式。要签名 SOAP 主体和签名令牌,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 需要指向包含解密私钥的密钥库。此外,必须使用指定密钥密码的 org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandlervalidationCallbackHandler 注入。

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

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 需要一个回调处理程序来获取密钥。因此,必须使用指向相应密钥库的 KeyStoreCallbackHandler 提供 securementCallbackHandler。默认情况下,结果 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-cbchttp://www.w3.org/2001/04/xmlenc#aes256-cbchttp://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 主体。

7.3.7. 安全异常处理

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



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

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

[5] 这是因为 WSS4J 仅需要用于加密密钥的 Crypto,而嵌入式密钥名称验证则委托给回调处理程序。

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