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

7.1. 简介

本章解释如何将 WS-Security 方面添加到您的 Web 服务中。我们将重点关注 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 Services Security 包 (XWSS)。此 WS-Security 实现是 Java Web Services Developer Pack (Java WSDP) 的一部分。

像任何其他端点拦截器一样,它在端点映射中定义(参见第 5.5 节,“端点映射”)。这意味着您可以有选择地添加 WS-Security 支持:有些端点映射需要它,而有些则不需要。

备注

请注意,XWSS 需要 SUN 1.5 JDK 和 SUN SAAJ 参考实现。WSS4J 拦截器没有这些要求(参见第 7.3 节,“ Wss4jSecurityInterceptor)。

XwsSecurityInterceptor 需要一个安全策略文件才能运行。此 XML 文件告诉拦截器从传入的 SOAP 消息中要求哪些安全方面,以及向传出消息中添加哪些方面。策略文件的基本格式将在以下章节中解释,但您可以在 此处 找到更深入的教程。您可以使用policyConfiguration属性设置策略,该属性需要一个 Spring 资源。策略文件可以包含多个元素,例如要求传入消息中包含用户名令牌,并对所有传出消息进行签名。它包含一个 SecurityConfiguration 元素作为根(而不是 JAXRPCSecurity 元素)。

此外,安全拦截器需要一个或多个 CallbackHandler 才能运行。这些处理程序用于检索证书、私钥、验证用户凭据等。Spring-WS 为大多数常见的安全问题提供处理程序,例如针对 Spring Security 身份验证管理器进行身份验证,基于 X509 证书对传出消息进行签名。以下章节将指出针对哪个安全问题使用哪个回调处理程序。您可以使用 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。如果未设置密钥或信任存储,回调处理程序将使用标准 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。在 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 身份验证和授权服务 。本文档超出了提供 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 故障。如果存在,它将向注册的处理程序触发一个带有 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 故障。如果存在,它将触发一个 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. 签名消息

签名消息时,XwsSecurityInterceptorBinarySecurityToken 和一个 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 服务安全: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>

验证操作包括

验证操作描述
用户名令牌 验证用户名令牌
时间戳 验证时间戳
加密 解密消息
签名 验证签名
无安全 未执行任何操作

安全措施是

安全操作描述
用户名令牌 添加用户名令牌
用户名令牌签名 添加用户名令牌和签名用户名令牌密钥
时间戳 添加时间戳
加密 加密响应
签名 签署响应
无安全 未执行任何操作

操作的顺序很重要,并由拦截器强制执行。如果传入 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 正文。

例如,以下是如何签署 Spring Web Services 回显示例中的 echoResponse 元素

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

WS Security 规范定义了几种格式来传输签名令牌(证书)或对这些令牌的引用。因此,纯元素名称 Token 对令牌进行签名,并处理不同的格式。要对 SOAP 正文和签名令牌进行签名,securementSignatureParts 的值必须包含

<property name="securementSignatureParts">
    <value>
        {}{http://schemas.xmlsoap.org/soap/envelope/}Body;
        Token
    </value>
</property>

要指定没有命名空间的元素,请使用字符串 Null 作为命名空间名称(区分大小写)。

如果在请求中没有其他局部名称为 Body 的元素,则 SOAP 命名空间标识符可以为空({})。

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 元素)的消息,请配置一个指向包含对称密钥的密钥库的 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 需要一个回调处理程序来获取密钥。因此,必须为 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-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 加密规范,了解元素和内容加密之间的差异。

© . This site is unofficial and not affiliated with VMware.