II. 参考

参考文档的这一部分详细介绍了构成 Spring Web Services 的各个组件。其中包括一章,讨论了客户端和服务端 WS 共有的部分,一章专门介绍编写服务端 Web 服务的具体内容,一章关于在客户端使用 Web 服务,以及一章关于使用WS-Security

4. 共享组件

本章探讨了客户端和服务器端 Spring-WS 开发之间共享的组件。这些接口和类代表了 Spring-WS 的构建块,因此即使您不直接使用它们,也需要了解它们的作用。spring-doc.cadn.net.cn

4.1. Web服务消息

本节描述了 Spring-WS 使用的消息和消息工厂。spring-doc.cadn.net.cn

4.1.1. WebServiceMessage

Spring Web Services 的核心接口之一是 WebServiceMessage。该接口表示与协议无关的 XML 消息。此接口包含一些方法,提供对消息有效负载的访问,形式为 javax.xml.transform.Sourcejavax.xml.transform.ResultSourceResult 是标记接口,代表对 XML 输入和输出的抽象。具体实现包装了各种 XML 表示形式,如下表所示:spring-doc.cadn.net.cn

源或结果实现 包装的 XML 表示

javax.xml.transform.dom.DOMSourcespring-doc.cadn.net.cn

org.w3c.dom.Nodespring-doc.cadn.net.cn

javax.xml.transform.dom.DOMResultspring-doc.cadn.net.cn

org.w3c.dom.Nodespring-doc.cadn.net.cn

javax.xml.transform.sax.SAXSourcespring-doc.cadn.net.cn

org.xml.sax.InputSourceorg.xml.sax.XMLReaderspring-doc.cadn.net.cn

javax.xml.transform.sax.SAXResultspring-doc.cadn.net.cn

org.xml.sax.ContentHandlerspring-doc.cadn.net.cn

javax.xml.transform.stream.StreamSourcespring-doc.cadn.net.cn

java.io.File, java.io.InputStream, or java.io.Readerspring-doc.cadn.net.cn

javax.xml.transform.stream.StreamResultspring-doc.cadn.net.cn

java.io.File, java.io.OutputStream, or java.io.Writerspring-doc.cadn.net.cn

除了从payload读取和写入之外,Web服务消息还可以将自身写入输出流。spring-doc.cadn.net.cn

4.1.2. SoapMessage

SoapMessageWebServiceMessage 的子类。它包含 SOAP 特定的方法,例如获取 SOAP 头部、SOAP 错误等。通常,您的代码不应依赖于 SoapMessage,因为 SOAP Body(消息的有效载荷)的内容可以通过在 WebServiceMessage 中使用 getPayloadSource()getPayloadResult() 来获取。只有在需要执行 SOAP 特定操作时(例如添加头部、获取附件等),才需要将 WebServiceMessage 转换为 SoapMessagespring-doc.cadn.net.cn

4.1.3. 消息工厂

Concrete message implementations are created by a WebServiceMessageFactory. This factory can create an empty message or read a message from an input stream. There are two concrete implementations of WebServiceMessageFactory. One is based on SAAJ, the SOAP with Attachments API for Java. The other is based on Axis 2’s AXIOM (AXis Object Model).spring-doc.cadn.net.cn

SaajSoapMessageFactory

SaajSoapMessageFactory 使用 Java 的 SOAP with Attachments API (SAAJ) 来创建 SoapMessage 实现。SAAJ 是 J2EE 1.4 的一部分,因此它应该在大多数现代应用服务器上得到支持。以下是常见应用服务器提供的 SAAJ 版本概述:spring-doc.cadn.net.cn

应用服务器 SAAJ 版本

BEA WebLogic 8spring-doc.cadn.net.cn

1.1spring-doc.cadn.net.cn

BEA WebLogic 9spring-doc.cadn.net.cn

1.1/1.21spring-doc.cadn.net.cn

IBM WebSphere 6spring-doc.cadn.net.cn

1.2spring-doc.cadn.net.cn

SUN Glassfish 1spring-doc.cadn.net.cn

1.3spring-doc.cadn.net.cn

1Weblogic 9 在 SAAJ 1.2 实现中有一个已知的错误:它实现了所有的 1.2 接口,但在调用时会抛出 UnsupportedOperationException。Spring Web Services 提供了一个解决方法:在 WebLogic 9 上运行时使用 SAAJ 1.1。spring-doc.cadn.net.cn

此外,Java SE 6 包含 SAAJ 1.3。您可以按如下方式连接一个 SaajSoapMessageFactoryspring-doc.cadn.net.cn

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJ 基于 DOM,即文档对象模型。这意味着所有的 SOAP 消息都存储在内存中。对于较大的 SOAP 消息,这可能不够高效。在这种情况下,AxiomSoapMessageFactory 可能更适用。
AxiomSoapMessageFactory

AxiomSoapMessageFactory 使用 AXis 2 对象模型 (AXIOM) 来创建 SoapMessage 实现。AXIOM 基于 StAX,即 XML 的流式 API。StAX 提供了一种基于拉取的机制来读取 XML 消息,这对于较大的消息来说可能更高效。spring-doc.cadn.net.cn

为了提高在 AxiomSoapMessageFactory 上的读取性能,可以将 payloadCaching 属性设置为 false(默认值为 true)。这样做会导致 SOAP 主体的内容直接从套接字流中读取。启用此设置后,负载只能读取一次。这意味着您必须确保消息的任何预处理(日志记录或其他操作)不会消耗它。spring-doc.cadn.net.cn

你可以如下使用 AxiomSoapMessageFactoryspring-doc.cadn.net.cn

<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
    <property name="payloadCaching" value="true"/>
</bean>

除了有效负载缓存之外,AXIOM 还支持完全流式传输的消息,如 StreamingWebServiceMessage 中所定义。这意味着您可以直接在响应消息上设置有效负载,而不是将其写入 DOM 树或缓冲区。spring-doc.cadn.net.cn

当处理方法返回一个受JAXB2支持的对象时,AXIOM会使用完整的流式传输。它会自动将此编组对象设置到响应消息中,并在响应发送出去时将其写入到传出的套接字流中。spring-doc.cadn.net.cn

有关完全流式的更多信息,请参阅 StreamingWebServiceMessageStreamingPayload 的类级 Javadoc。spring-doc.cadn.net.cn

SOAP 1.1 或 1.2

Both the SaajSoapMessageFactory and the AxiomSoapMessageFactory have a soapVersion property, where you can inject a SoapVersion constant. By default, the version is 1.1, but you can set it to 1.2:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd">

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="soapVersion">
            <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/>
        </property>
    </bean>

</beans>

在前面的示例中,我们定义了一个SaajSoapMessageFactory,它仅接受SOAP 1.2消息。spring-doc.cadn.net.cn

尽管两个版本的 SOAP 在格式上非常相似,但 1.2 版本与 1.1 版本并不向后兼容,因为它使用了不同的 XML 命名空间。SOAP 1.1 和 1.2 之间的其他主要区别包括错误消息结构的不同以及 SOAPAction HTTP 头实际上已被弃用(尽管它们仍然可以使用)的事实。spring-doc.cadn.net.cn

需要注意的一件重要事情是,SOAP版本号(或WS-*规范版本号)的最新版本通常不是最流行的版本。对于SOAP来说,这意味着(当前)使用的最佳版本是1.1。虽然1.2版未来可能会变得更流行,但目前来看,选择1.1版是最安全的选择。spring-doc.cadn.net.cn

4.1.4. MessageContext

通常,消息成对出现:一个请求和一个响应。请求在客户端创建,通过某种传输方式发送到服务器端,在服务器端生成响应。该响应被发回客户端,并在客户端读取。spring-doc.cadn.net.cn

在 Spring Web Services 中,这样的对话包含在一个 MessageContext 中,它具有获取请求和响应消息的属性。在客户端,消息上下文由 WebServiceTemplate 创建。在服务器端,消息上下文从特定于传输的输入流中读取。例如,在 HTTP 中,它从 HttpServletRequest 中读取,而响应则写回到 HttpServletResponsespring-doc.cadn.net.cn

4.2. TransportContext

SOAP 协议的关键特性之一是它试图做到传输无关性。这就是为什么,例如,Spring-WS 不支持通过 HTTP 请求 URL 将消息映射到端点,而是通过消息内容来实现。spring-doc.cadn.net.cn

然而,有时有必要在客户端或服务器端访问底层传输。为此,Spring Web Services 提供了 TransportContext。传输上下文允许访问底层的 WebServiceConnection,通常在服务器端是一个 HttpServletConnection,而在客户端则是一个 HttpUrlConnectionCommonsHttpConnection。例如,您可以在服务器端的端点或拦截器中获取当前请求的 IP 地址:spring-doc.cadn.net.cn

TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();

4.3. 使用XPath处理XML

处理 XML 的最佳方法之一是使用 XPath。引用 [effective-xml],第 35 条:spring-doc.cadn.net.cn

XPath 是一种第四代声明性语言,它允许您指定要处理的节点,而无需明确说明处理器应该如何导航到这些节点。XPath 的数据模型设计得非常出色,几乎可以支持所有开发者对 XML 的需求。例如,它合并所有相邻的文本,包括 CDATA 部分中的文本,允许计算跳过注释和处理指令并包含来自子元素和后代元素的文本值,且要求解析所有外部实体引用。在实际应用中,XPath 表达式往往对输入文档中意外但可能不重要的更改具有更强的适应性。
— Elliotte Rusty Harold

Spring Web Services 在您的应用程序中有两种使用 XPath 的方式:更快的 XPathExpression 或更灵活的 XPathTemplatespring-doc.cadn.net.cn

4.3.1. XPathExpression

XPathExpression 是对已编译 XPath 表达式的抽象,例如 Java 5 的 javax.xml.xpath.XPathExpression 接口或 Jaxen 的 XPath 类。要在应用程序上下文中构造表达式,可以使用 XPathExpressionFactoryBean。以下示例使用了这个工厂 bean:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="nameExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
        <property name="expression" value="/Contacts/Contact/Name"/>
    </bean>

    <bean id="myEndpoint" class="sample.MyXPathClass">
        <constructor-arg ref="nameExpression"/>
    </bean>

</beans>

前面的表达式没有使用命名空间,但我们可以使用工厂 bean 的 namespaces 属性来设置命名空间。该表达式可以在代码中如下使用:spring-doc.cadn.net.cn

package sample;

public class MyXPathClass {

    private final XPathExpression nameExpression;

    public MyXPathClass(XPathExpression nameExpression) {
        this.nameExpression = nameExpression;
    }

    public void doXPath(Document document) {
        String name = nameExpression.evaluateAsString(document.getDocumentElement());
        System.out.println("Name: " + name);
    }

}

对于更灵活的方法,您可以使用NodeMapper,它类似于Spring的JDBC支持中的RowMapper。以下示例显示了如何使用它:spring-doc.cadn.net.cn

package sample;

public class MyXPathClass  {

   private final XPathExpression contactExpression;

   public MyXPathClass(XPathExpression contactExpression) {
      this.contactExpression = contactExpression;
   }

   public void doXPath(Document document) {
      List contacts = contactExpression.evaluate(document,
        new NodeMapper() {
           public Object mapNode(Node node, int nodeNum) throws DOMException {
              Element contactElement = (Element) node;
              Element nameElement = (Element) contactElement.getElementsByTagName("Name").item(0);
              Element phoneElement = (Element) contactElement.getElementsByTagName("Phone").item(0);
              return new Contact(nameElement.getTextContent(), phoneElement.getTextContent());
           }
        });
      PlainText Section qName; // do something with the list of Contact objects
   }
}

类似于 Spring JDBC 的 RowMapper 中的映射行,每个结果节点都通过使用匿名内部类进行映射。在这种情况下,我们创建了一个 Contact 对象,以便稍后使用。spring-doc.cadn.net.cn

4.3.2. XPathTemplate

The XPathExpression lets you evaluate only a single, pre-compiled expression. A more flexible, though slower, alternative is the XpathTemplate. This class follows the common template pattern used throughout Spring (JdbcTemplate, JmsTemplate, and others). The following listing shows an example:spring-doc.cadn.net.cn

package sample;

public class MyXPathClass {

    private XPathOperations template = new Jaxp13XPathTemplate();

    public void doXPath(Source source) {
        String name = template.evaluateAsString("/Contacts/Contact/Name", request);
        // do something with name
    }

}

4.4. 消息日志记录与追踪

当开发或调试 Web 服务时,在消息(SOAP)到达或发送之前查看其内容可能会非常有用。Spring Web Services 通过标准的 Commons Logging 接口提供了此功能。spring-doc.cadn.net.cn

确保使用 Commons Logging 版本 1.1 或更高版本。早期版本存在类加载问题,并且无法与 Log4J 的 TRACE 级别集成。

要记录所有服务器端的消息,请将org.springframework.ws.server.MessageTracing日志级别设置为DEBUGTRACE。在DEBUG级别,仅记录有效负载的根元素。在TRACE级别,将记录整个消息内容。如果只想记录发送的消息,请使用org.springframework.ws.server.MessageTracing.sent日志记录器。同样,您可以使用org.springframework.ws.server.MessageTracing.received来仅记录接收到的消息。spring-doc.cadn.net.cn

在客户端,存在类似的记录器:org.springframework.ws.client.MessageTracing.sentorg.springframework.ws.client.MessageTracing.receivedspring-doc.cadn.net.cn

以下 log4j.properties 配置文件的示例在客户端记录发送消息的完整内容,而仅记录客户端接收到的消息的有效负载根元素。在服务器端,发送和接收的消息都会记录有效负载根元素:spring-doc.cadn.net.cn

log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE
log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG

log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n

通过此配置,典型的输出是:spring-doc.cadn.net.cn

TRACE [client.MessageTracing.sent] Sent request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="...
DEBUG [server.MessageTracing.received] Received request [SaajSoapMessage {http://example.com}request] ...
DEBUG [server.MessageTracing.sent] Sent response [SaajSoapMessage {http://example.com}response] ...
DEBUG [client.MessageTracing.received] Received response [SaajSoapMessage {http://example.com}response] ...

5. 使用 Spring-WS 创建 Web 服务

Spring-WS的服务器端支持是围绕一个MessageDispatcher设计的,该组件将传入的消息分发到端点,并提供可配置的端点映射、响应生成和端点拦截功能。端点通常使用@Endpoint注解进行标注,并具有一个或多个处理方法。这些方法通过检查消息的某些部分(通常是有效负载)来处理传入的XML请求消息,并创建某种响应。您可以使用另一个注解(通常是@PayloadRoot)对方法进行标注,以指示它可以处理的消息类型。spring-doc.cadn.net.cn

Spring-WS 的 XML 处理非常灵活。端点可以从 Spring-WS 支持的大量 XML 处理库中进行选择,包括:spring-doc.cadn.net.cn

5.1. MessageDispatcher

Spring-WS 的服务器端围绕一个核心类设计,该类将传入的 XML 消息分派到端点。Spring-WS 的 MessageDispatcher 非常灵活,允许您使用任何类型的类作为端点,只要它可以在 Spring IoC 容器中配置即可。在某种程度上,消息分发器类似于 Spring 的 DispatcherServlet,即 Spring Web MVC 中使用的“前端控制器”。spring-doc.cadn.net.cn

以下序列图展示了 MessageDispatcher 的处理和分发流程:spring-doc.cadn.net.cn

sequence

当设置 MessageDispatcher 以供使用,并且针对该特定调度器的请求到达时,MessageDispatcher 开始处理请求。以下过程描述了 MessageDispatcher 如何处理请求:spring-doc.cadn.net.cn

  1. 配置的 EndpointMapping(s) 将被搜索以找到合适的端点。如果找到端点,则会调用与该端点关联的调用链(预处理器、后处理器和端点)以创建响应。spring-doc.cadn.net.cn

  2. 找到了适用于该端点的适配器。 MessageDispatcher 将委托给此适配器以调用端点。spring-doc.cadn.net.cn

  3. 如果返回了响应,则会将其发送出去。如果没有返回响应(例如,由于前置或后置处理器拦截了请求,出于安全原因),则不会发送任何响应。spring-doc.cadn.net.cn

在处理请求期间抛出的异常会被应用程序上下文中声明的任何端点异常解析器捕获。使用这些异常解析器可以让你定义自定义行为(例如返回一个SOAP错误),以防抛出此类异常。spring-doc.cadn.net.cn

MessageDispatcher 有多个属性用于设置端点适配器、映射异常解析器。然而,这些属性的设置并不是必需的,因为调度器会自动检测应用程序上下文中注册的所有类型。只有在需要覆盖检测时,才应该设置这些属性。spring-doc.cadn.net.cn

消息调度器是基于消息上下文进行操作的,而不是基于特定传输的输入流和输出流。因此,特定传输的请求需要读取到一个MessageContext中。对于HTTP来说,这是通过一个WebServiceMessageReceiverHandlerAdapter(这是一个Spring Web中的HandlerInterceptor)来完成的,以便MessageDispatcher可以被装配到标准的DispatcherServlet中。然而,有一种更便捷的方式来实现这一点,如MessageDispatcherServlet所示。spring-doc.cadn.net.cn

5.2. 传输方式

Spring Web Services 支持多种传输协议。最常见的是 HTTP 传输,为此提供了自定义的 servlet,但您也可以通过 JMS 甚至电子邮件发送消息。spring-doc.cadn.net.cn

5.2.1. MessageDispatcherServlet

The MessageDispatcherServlet 是一个标准的 Servlet,它方便地从标准的 Spring Web DispatcherServlet 扩展而来,并包装了一个 MessageDispatcher。因此,它将这些属性组合在一起。作为一个 MessageDispatcher,它遵循与上一节中描述的相同请求处理流程。作为一个 servlet,MessageDispatcherServlet 在您的 Web 应用程序的 web.xml 中进行配置。您希望 MessageDispatcherServlet 处理的请求必须通过同一个 web.xml 文件中的 URL 映射来映射。这是标准的 Java EE servlet 配置。以下示例展示了这样的 MessageDispatcherServlet 声明和映射:spring-doc.cadn.net.cn

<web-app>

    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

在前面的示例中,所有请求都由spring-wsMessageDispatcherServlet处理。这只是设置 Spring Web Services 的第一步,因为 Spring-WS 框架使用的各种组件 bean 也需要进行配置。此配置由标准的 Spring XML<bean/>定义组成。由于MessageDispatcherServlet是一个标准的 SpringDispatcherServlet,它会在您的 Web 应用程序的WEB-INF目录中查找名为 [servlet-name]-servlet.xml 的文件,并在 Spring 容器中创建那里定义的 bean。在前面的示例中,它会查找 ‘/WEB-INF/spring-ws-servlet.xml’。该文件包含所有的 Spring Web Services bean,例如端点、编组器等。spring-doc.cadn.net.cn

作为 web.xml 的替代方法,如果你运行在 Servlet 3+ 环境中,你可以通过编程方式配置 Spring-WS。为此,Spring-WS 提供了一些抽象基类,这些基类扩展了 Spring 框架中的 WebApplicationInitializer 接口。如果你还使用 @Configuration 类来定义你的 bean,你应该扩展 AbstractAnnotationConfigMessageDispatcherServletInitializerspring-doc.cadn.net.cn

public class MyServletInitializer
    extends AbstractAnnotationConfigMessageDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{MyRootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyEndpointConfig.class};
    }

}

在前面的示例中,我们告诉 Spring 可以在 MyEndpointConfig 类(这是一个 @Configuration 类)中找到端点 bean 定义。其他 bean 定义(通常是服务、存储库等)可以在 MyRootConfig 类中找到。默认情况下,AbstractAnnotationConfigMessageDispatcherServletInitializer 将 servlet 映射到两个模式:/services*.wsdl,不过您可以通过重写 getServletMappings() 方法来更改此行为。有关 MessageDispatcherServlet 的编程配置的更多详细信息,请参阅 AbstractMessageDispatcherServletInitializerAbstractAnnotationConfigMessageDispatcherServletInitializer 的 Javadoc。spring-doc.cadn.net.cn

自动WSDL暴露

MessageDispatcherServlet 会自动检测其 Spring 容器中定义的任何 WsdlDefinition bean。所有检测到的 WsdlDefinition bean 也会通过 WsdlDefinitionHandlerAdapter 暴露出来。这是一种通过定义一些 bean 向客户端公开 WSDL 的便捷方法。spring-doc.cadn.net.cn

通过一个示例来考虑以下<static-wsdl>定义,该定义在Spring-WS配置文件(/WEB-INF/[servlet-name]-servlet.xml)中。请注意id属性的值,因为它在暴露WSDL时会被使用。spring-doc.cadn.net.cn

<sws:static-wsdl id="orders" location="orders.wsdl"/>

Alternatively, it can be a @Bean method in a @Configuration class:spring-doc.cadn.net.cn

@Bean
public SimpleWsdl11Definition orders() {
	return new SimpleWsdl11Definition(new ClassPathResource("orders.wsdl"));
}

您可以通过以下形式的URL(根据需要替换主机、端口和servlet上下文路径)以GET请求方式访问类路径中定义在orders.wsdl文件中的WSDL:spring-doc.cadn.net.cn

http://localhost:8080/spring-ws/orders.wsdl
所有 WsdlDefinition 个 bean 定义都通过 MessageDispatcherServlet 以它们的 bean 名称加上后缀 `.wsdl` 的形式暴露出来。因此,如果 bean 名称是 echo,主机名是 server,而 Servlet 上下文(war 名称)是 spring-ws,那么 WSDL 可以在 http://server/spring-ws/echo.wsdl 找到。

MessageDispatcherServlet(或者更准确地说是WsdlDefinitionHandlerAdapter)的另一个不错的功能是,它可以转换所有公开的 WSDL 的location值,以反映传入请求的 URL。spring-doc.cadn.net.cn

请注意,默认情况下此 location 转换功能是关闭的。要启用此功能,您需要为 MessageDispatcherServlet 指定一个初始化参数:spring-doc.cadn.net.cn

<web-app>

  <servlet>
    <servlet-name>spring-ws</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    <init-param>
      <param-name>transformWsdlLocations</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>spring-ws</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

如果使用 AbstractAnnotationConfigMessageDispatcherServletInitializer,启用转换就像重写 isTransformWsdlLocations() 方法以返回 true 一样简单。spring-doc.cadn.net.cn

Consult the class-level Javadoc on the WsdlDefinitionHandlerAdapter class to learn more about the whole transformation process.spring-doc.cadn.net.cn

作为手动编写 WSDL 并通过 <static-wsdl> 暴露它的替代方案,Spring Web Services 还可以从 XSD 模式生成 WSDL。这种方法在 发布 WSDL 中展示。下一个应用上下文片段展示了如何创建这样的动态 WSDL 文件:spring-doc.cadn.net.cn

<sws:dynamic-wsdl id="orders"
    portTypeName="Orders"
    locationUri="http://localhost:8080/ordersService/">
  <sws:xsd location="Orders.xsd"/>
</sws:dynamic-wsdl>

或者,您可以使用 Java @Bean 方法:spring-doc.cadn.net.cn

@Bean
public DefaultWsdl11Definition orders() {
    DefaultWsdl11Definition definition = new DefaultWsdl11Definition();
    definition.setPortTypeName("Orders");
    definition.setLocationUri("http://localhost:8080/ordersService/");
    definition.setSchema(new SimpleXsdSchema(new ClassPathResource("echo.xsd")));

    return definition;
}

<dynamic-wsdl> 元素依赖于 DefaultWsdl11Definition 类。此类定义使用 org.springframework.ws.wsdl.wsdl11.provider 包中的 WSDL 提供程序和 ProviderBasedWsdl4jDefinition 类,在首次请求时生成 WSDL。如果需要,可以查看这些类的类级别 Javadoc,了解如何扩展此机制。spring-doc.cadn.net.cn

DefaultWsdl11Definition(因此,<dynamic-wsdl> 标签)通过使用约定从 XSD 模式构建 WSDL。它会遍历模式中找到的所有 element 元素,并为所有元素创建一个 message。接下来,它会为所有以定义的请求或响应后缀结尾的消息创建一个 WSDL operation。默认的请求后缀是 Request,默认的响应后缀是 Response,尽管可以通过分别在 <dynamic-wsdl /> 上设置 requestSuffixresponseSuffix 属性来更改这些后缀。它还会根据操作构建 portTypebindingservicespring-doc.cadn.net.cn

例如,如果我们的 Orders.xsd 模式定义了 GetOrdersRequestGetOrdersResponse 元素,<dynamic-wsdl> 将创建一个 GetOrdersRequestGetOrdersResponse 消息以及一个 GetOrders 操作,这些会被放入 Orders 端口类型中。spring-doc.cadn.net.cn

要使用多个模式(schema),无论是通过包含(includes)还是导入(imports),都可以将 Commons XMLSchema 放在类路径中。如果 Commons XMLSchema 在类路径中,<dynamic-wsdl> 元素会跟踪所有的 XSD 导入和包含,并将它们作为单个 XSD 内联到 WSDL 中。这大大简化了模式的部署,同时仍然可以分别编辑它们。spring-doc.cadn.net.cn

尽管在运行时根据您的 XSD 创建 WSDL 可能很方便,但这种方法也有一些缺点。首先,虽然我们力求在各个版本之间保持 WSDL 生成过程的一致性,但仍有可能发生(细微的)变化。其次,生成过程有点慢,不过一旦生成,WSDL 将被缓存以供后续使用。

因此,您应该仅在项目的开发阶段使用 <dynamic-wsdl>。我们建议使用浏览器下载生成的 WSDL,将其存储在项目中,并通过 <static-wsdl> 对其进行暴露。这是确保 WSDL 不会随着时间而改变的唯一方法。spring-doc.cadn.net.cn

5.2.2. 在 Spring-WS 中进行装配DispatcherServlet

作为 MessageDispatcherServlet 的替代方案,您可以在标准的 Spring-Web MVC DispatcherServlet 中连接一个 MessageDispatcher。默认情况下,DispatcherServlet 只能委派给 Controllers,但我们可以指示它通过在 servlet 的 Web 应用程序上下文中添加一个 WebServiceMessageReceiverHandlerAdapter 来委派给 MessageDispatcherspring-doc.cadn.net.cn

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    ...

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

</beans>

请注意,通过显式添加WebServiceMessageReceiverHandlerAdapter,调度器 servlet 不会加载默认的适配器,因此无法处理标准的 Spring-MVC @Controllers。因此,我们在最后添加了RequestMappingHandlerAdapterspring-doc.cadn.net.cn

同样地,您可以连接一个 WsdlDefinitionHandlerAdapter 以确保 DispatcherServlet 能够处理 WsdlDefinition 接口的实现:spring-doc.cadn.net.cn

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
           <props>
             <prop key="*.wsdl">myServiceDefinition</prop>
           </props>
        </property>
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    <bean id="myServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
       <prop name="wsdl" value="/WEB-INF/myServiceDefintion.wsdl"/>
    </bean>

    ...

</beans>

5.2.3. JMS 传输

Spring Web Services通过Spring框架提供的JMS功能支持服务器端的JMS处理。Spring Web Services提供了WebServiceMessageListener以插入到MessageListenerContainer中。此消息监听器需要一个WebServiceMessageFactoryMessageDispatcher才能运行。以下配置示例展示了这一点:spring-doc.cadn.net.cn

<beans>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
    </bean>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destinationName" value="RequestQueue"/>
        <property name="messageListener">
            <bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
                <property name="messageFactory" ref="messageFactory"/>
                <property name="messageReceiver" ref="messageDispatcher"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.2.4. 邮件传输

除了 HTTP 和 JMS,Spring Web Services 还提供了服务器端的电子邮件处理功能。此功能通过 MailMessageReceiver 类提供。该类监控一个 POP3 或 IMAP 文件夹,将电子邮件转换为 WebServiceMessage,并使用 SMTP 发送任何响应。您可以通过 storeUri 配置主机名,它表示用于监控请求的邮件文件夹(通常是 POP3 或 IMAP 文件夹),以及一个 transportUri,它表示用于发送响应的服务器(通常是一个 SMTP 服务器)。spring-doc.cadn.net.cn

你可以通过一个可插拔的策略来配置MailMessageReceiver如何监控传入的消息:即MonitoringStrategy。默认情况下,使用轮询策略,每五分钟轮询一次收件文件夹是否有新消息。你可以通过在策略上设置pollingInterval属性来更改此间隔。默认情况下,所有MonitoringStrategy实现都会删除已处理的消息。你可以通过设置deleteMessages属性来更改此设置。spring-doc.cadn.net.cn

作为轮询方法的一种替代方案,轮询方法效率较低,有一种使用 IMAP IDLE 的监控策略。IDLE 命令是 IMAP 电子邮件协议的可选扩展,它允许邮件服务器异步地向 MailMessageReceiver 发送新消息更新。如果您使用支持 IDLE 命令的 IMAP 服务器,则可以将 ImapIdleMonitoringStrategy 插入到 monitoringStrategy 属性中。除了需要支持的服务器外,您还需要使用 JavaMail 1.4.1 或更高版本。spring-doc.cadn.net.cn

以下配置片段展示了如何使用服务器端的电子邮件支持,并将默认的轮询间隔覆盖为每30秒(30,000毫秒)检查一次:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="from" value="Spring-WS SOAP Server &lt;[email protected]&gt;"/>
        <property name="storeUri" value="imap://server:[email protected]/INBOX"/>
        <property name="transportUri" value="smtp://smtp.example.com"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
        <property name="monitoringStrategy">
            <bean class="org.springframework.ws.transport.mail.monitor.PollingMonitoringStrategy">
                <property name="pollingInterval" value="30000"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.2.5. 嵌入式 HTTP 服务器传输

Spring Web Services 提供了基于 Sun 的 JRE 1.6 HTTP 服务器 的传输方式。嵌入式 HTTP 服务器是一个独立的服务器,配置简单。它提供了一种比传统 servlet 容器更轻量的替代方案。spring-doc.cadn.net.cn

当使用嵌入式HTTP服务器时,您不需要外部部署描述符(web.xml)。您只需定义服务器的一个实例,并对其进行配置以处理传入的请求。核心Spring框架中的远程模块包含一个用于HTTP服务器的便捷工厂bean:SimpleHttpServerFactoryBean。最重要的属性是contexts,它将上下文路径映射到相应的HttpHandler实例。spring-doc.cadn.net.cn

Spring Web Services 提供了两种 HttpHandler 接口的实现:WsdlDefinitionHttpHandlerWebServiceMessageReceiverHttpHandler。前者将传入的 GET 请求映射到 WsdlDefinition。后者负责处理 Web 服务消息的 POST 请求,因此需要一个 WebServiceMessageFactory(通常是一个 SaajSoapMessageFactory)和一个 WebServiceMessageReceiver(通常是 SoapMessageDispatcher)来完成其任务。spring-doc.cadn.net.cn

为了与servlet世界进行类比,contexts属性在web.xml中扮演了servlet映射的角色,而WebServiceMessageReceiverHttpHandler等同于一个MessageDispatcherServletspring-doc.cadn.net.cn

以下代码片段展示了HTTP服务器传输的配置示例:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings" ref="endpointMapping"/>
    </bean>

    <bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
        <property name="defaultEndpoint" ref="stockEndpoint"/>
    </bean>

    <bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
        <property name="contexts">
            <map>
                <entry key="/StockService.wsdl" value-ref="wsdlHandler"/>
                <entry key="/StockService" value-ref="soapHandler"/>
            </map>
        </property>
    </bean>

    <bean id="soapHandler" class="org.springframework.ws.transport.http.WebServiceMessageReceiverHttpHandler">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="messageReceiver" ref="messageReceiver"/>
    </bean>

    <bean id="wsdlHandler" class="org.springframework.ws.transport.http.WsdlDefinitionHttpHandler">
        <property name="definition" ref="wsdlDefinition"/>
    </bean>
</beans>

有关 SimpleHttpServerFactoryBean 的更多信息,请参阅 Javadocspring-doc.cadn.net.cn

5.2.6. XMPP 传输

Spring Web Services 2.0 引入了对 XMPP(也称为 Jabber)的支持。该支持基于 Smack 库。spring-doc.cadn.net.cn

Spring Web Services 对 XMPP 的支持与其他传输方式非常相似:有一个用于的 XmppMessageSender 和一个与之配合使用的 XmppMessageReceiverWebServiceTemplatespring-doc.cadn.net.cn

以下示例展示了如何设置服务器端的 XMPP 组件:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="connection" ref="connection"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>

</beans>

5.2.7. MTOM

MTOM 是用于向 Web 服务发送和接收二进制数据的机制。你可以通过 MTOM 示例 了解如何使用 Spring WS 实现这一功能。spring-doc.cadn.net.cn

5.3. 端点

Endpoints 是 Spring-WS 服务器端支持的核心概念。Endpoints 提供对应用程序行为的访问,这些行为通常由业务服务接口定义。一个 Endpoint 解析 XML 请求消息,并使用该输入(通常)调用业务服务上的方法。该服务调用的结果被表示为一个响应消息。Spring-WS 拥有各种各样的 Endpoints,并采用多种方式处理 XML 消息以及创建响应。spring-doc.cadn.net.cn

你可以通过使用@Endpoint注解来标注一个类,从而创建一个端点。在该类中,你可以定义一个或多个方法来处理传入的XML请求,这些方法可以使用多种参数类型(例如DOM元素、JAXB2对象等)。你可以通过使用另一个注解(通常是@PayloadRoot)来指定某个方法能够处理的消息类型。spring-doc.cadn.net.cn

考虑以下示例端点:spring-doc.cadn.net.cn

package samples;

import org.w3c.dom.Element;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.soap.SoapHeader;

@Endpoint                                                                                      (1)
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  @Autowired                                                                                   (2)
  public AnnotationOrderEndpoint(OrderService orderService) {
      this.orderService = orderService;
  }

  @PayloadRoot(localPart = "order", namespace = "http://samples")                              (5)
  public void order(@RequestPayload Element orderElement) {                                    (3)
    Order order = createOrder(orderElement);
    orderService.createOrder(order);
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")                       (5)
  @ResponsePayload
  public Order getOrder(@RequestPayload OrderRequest orderRequest, SoapHeader header) {        (4)
    checkSoapHeaderForSomething(header);
    return orderService.getOrder(orderRequest.getId());
  }

  ...

}
1 类使用 @Endpoint 注解标记,将其标识为 Spring-WS 端点。
2 构造函数标记为 @Autowired,以便将 OrderService 业务服务注入到此端点中。
3 The order 方法接受一个 Element (使用 @RequestPayload 注解)作为参数。这意味着消息的有效负载将作为 DOM 元素传递给该方法。该方法的返回类型为 void,表示不会发送响应消息。 有关端点方法的更多信息,请参见 @Endpoint 处理方法
4 getOrder 方法接受一个 OrderRequest(同样用 @RequestPayload 注解)作为参数。该参数是一个受 JAXB2 支持的对象(它用 @XmlRootElement 注解)。这意味着消息的有效载荷会作为反序列化的对象传递给此方法。SoapHeader 类型也作为参数提供。在调用时,该参数包含请求消息的 SOAP 头部。该方法还用 @ResponsePayload 注解,表明返回值(即 Order)将被用作响应消息的有效载荷。 有关端点方法的更多信息,请参阅 @Endpoint 处理方法
5 此端点的两种处理方法被标记为@PayloadRoot,表示该方法可以处理何种请求消息:getOrder方法会在请求的本地名称为orderRequest且命名空间URI为http://samples时被调用。order方法则会在请求的本地名称为order时被调用。 有关@PayloadRoot的更多信息,请参阅端点映射

要启用对 @Endpoint 和相关的 Spring-WS 注解的支持,您需要将以下内容添加到您的 Spring 应用程序上下文中:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sws="http://www.springframework.org/schema/web-services"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/web-services
      http://www.springframework.org/schema/web-services/web-services.xsd">

  *<sws:annotation-driven />

</beans>

或者,如果你使用 @Configuration 类而不是 Spring XML,则可以使用 @EnableWs 注解你的配置类:spring-doc.cadn.net.cn

@EnableWs
@Configuration
public class EchoConfig {

    // @Bean definitions go here

}

要自定义 @EnableWs 配置,您可以实现 WsConfigurer,或者更好的方法是扩展 WsConfigurerAdapterspring-doc.cadn.net.cn

@Configuration
@EnableWs
@ComponentScan(basePackageClasses = { MyConfiguration.class })
public class MyConfiguration extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyInterceptor());
  }

  @Override
  public void addArgumentResolvers(List<MethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new MyArgumentResolver());
  }

  // More overridden methods ...
}

在接下来的几个部分中,将对@Endpoint编程模型进行更详细的描述。spring-doc.cadn.net.cn

Endpoints, 像其他任何 Spring Bean 一样,默认情况下是单例作用域的。也就是说,每个容器会根据 bean 定义创建一个实例。作为单例意味着可能会有多个线程同时使用它,因此该端点必须是线程安全的。如果你想使用不同的作用域,例如 prototype,请参阅Spring 参考文档
Note that all abstract base classes provided in Spring-WS are thread safe, unless otherwise indicated in the class-level Javadoc.

5.3.1. @Endpoint 处理方法

为了使某个端点能够实际处理传入的 XML 消息,它需要具有一个或多个处理方法。处理方法可以接受广泛范围的参数和返回类型。然而,它们通常会有一个包含消息有效负载的参数,并返回响应消息的有效负载(如果有的话)。本节将介绍支持哪些参数和返回类型。spring-doc.cadn.net.cn

为了指示方法可以处理何种类型的消息,该方法通常使用@PayloadRoot@SoapAction注解进行标注。您可以在端点映射中了解更多关于这些注解的信息。spring-doc.cadn.net.cn

以下示例展示了一个处理方法:spring-doc.cadn.net.cn

@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(@RequestPayload Element orderElement) {
  Order order = createOrder(orderElement);
  orderService.createOrder(order);
}

The order method takes an Element (annotated with @RequestPayload) as a parameter. This means that the payload of the message is passed on this method as a DOM element. The method has a void return type, indicating that no response message is sent.spring-doc.cadn.net.cn

处理方法参数

处理方法通常具有一个或多个参数,这些参数引用传入XML消息的不同部分。最常见的情况是,处理方法具有单一参数,该参数映射到消息的有效负载,但它也可以映射到请求消息的其他部分,例如SOAP头。本节描述了您可以在处理方法签名中使用的参数。spring-doc.cadn.net.cn

要将参数映射到请求消息的有效负载,您需要使用 @RequestPayload 注解对该参数进行注释。此注解告诉 Spring-WS 该参数需要绑定到请求的有效负载。spring-doc.cadn.net.cn

下表描述了支持的参数类型。它显示了支持的类型,参数是否应使用 @RequestPayload 进行注解,以及任何附加说明。spring-doc.cadn.net.cn

姓名 支持的参数类型 @RequestPayload 是必需的吗? 附加说明

TrAXspring-doc.cadn.net.cn

javax.xml.transform.Source 和子接口 (DOMSource, SAXSource, StreamSource, 和 StAXSource)spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

W3C DOMspring-doc.cadn.net.cn

org.w3c.dom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用spring-doc.cadn.net.cn

dom4jspring-doc.cadn.net.cn

org.dom4j.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 dom4j 在类路径中时启用。spring-doc.cadn.net.cn

JDOMspring-doc.cadn.net.cn

org.jdom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JDOM 在类路径中时启用。spring-doc.cadn.net.cn

XOMspring-doc.cadn.net.cn

nu.xom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 XOM 在类路径中时启用。spring-doc.cadn.net.cn

StAXspring-doc.cadn.net.cn

javax.xml.stream.XMLStreamReaderjavax.xml.stream.XMLEventReaderspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 StAX 在类路径中时启用。spring-doc.cadn.net.cn

XPathspring-doc.cadn.net.cn

任何布尔值、双精度浮点数、Stringorg.w3c.Nodeorg.w3c.dom.NodeList,或者可以通过 Spring 的 转换服务String 转换而来的类型,并且使用 @XPathParam 注解。spring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认情况下已启用,参见名为XPathParam的部分spring-doc.cadn.net.cn

消息上下文spring-doc.cadn.net.cn

org.springframework.ws.context.MessageContextspring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

SOAPspring-doc.cadn.net.cn

org.springframework.ws.soap.SoapMessage, org.springframework.ws.soap.SoapBody, org.springframework.ws.soap.SoapEnvelope, org.springframework.ws.soap.SoapHeader, 和 org.springframework.ws.soap.SoapHeaderElement`s when used in combination with the `@SoapHeader 注解。spring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

JAXB2spring-doc.cadn.net.cn

任何使用 javax.xml.bind.annotation.XmlRootElementjavax.xml.bind.JAXBElement 注解的类型。spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JAXB2 在类路径中时启用。spring-doc.cadn.net.cn

OXMspring-doc.cadn.net.cn

任何受 Spring OXM 支持的类型 Unmarshallerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当指定了 <sws:annotation-driven/>unmarshaller 属性时启用。spring-doc.cadn.net.cn

接下来的几个示例展示了可能的方法签名。以下方法在请求消息的有效负载作为 DOM org.w3c.dom.Element 调用时触发:spring-doc.cadn.net.cn

public void handle(@RequestPayload Element element)

以下方法使用请求消息的有效负载作为 javax.xml.transform.dom.DOMSource 调用。 header 参数绑定到请求消息的 SOAP 头。spring-doc.cadn.net.cn

public void handle(@RequestPayload DOMSource domSource, SoapHeader header)

以下方法被调用时,请求消息的有效载荷会被解组为一个MyJaxb2Object(其使用@XmlRootElement进行注解)。消息的有效载荷也会作为 DOM Element 提供。整个消息上下文作为第三个参数传递。spring-doc.cadn.net.cn

public void handle(@RequestPayload MyJaxb2Object requestObject, @RequestPayload Element element, Message messageContext)

正如您所看到的,在定义如何处理方法签名时,有很多种可能性。您甚至可以扩展此机制以支持您自己的参数类型。请参阅DefaultMethodEndpointAdapterMethodArgumentResolver 的 Javadoc,了解具体方法。spring-doc.cadn.net.cn

@XPathParam

One parameter type needs some extra explanation: @XPathParam. The idea here is that you annotate one or more method parameters with an XPath expression and that each such annotated parameter is bound to the evaluation of the expression. The following example shows how to do so:spring-doc.cadn.net.cn

package samples;

import javax.xml.transform.Source;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;

@Endpoint
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  public AnnotationOrderEndpoint(OrderService orderService) {
    this.orderService = orderService;
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
  @Namespace(prefix = "s", uri="http://samples")
  public Order getOrder(@XPathParam("/s:orderRequest/@id") int orderId) {
    Order order = orderService.getOrder(orderId);
    // create Source from order and return it
  }

}

由于我们在XPath表达式中使用了s前缀,因此必须将其绑定到http://samples命名空间。这是通过@Namespace注解实现的。或者,我们可以将此注解放置在类型级别,以便对所有处理器方法使用相同的命名空间映射,甚至可以放置在包级别(在package-info.java中),以用于多个端点。spring-doc.cadn.net.cn

通过使用 @XPathParam,您可以绑定 XPath 支持的所有数据类型:spring-doc.cadn.net.cn

除了此列表之外,您还可以使用任何可以通过 Spring 转换服务String 转换的类型。spring-doc.cadn.net.cn

处理方法返回类型

要发送响应消息,处理程序需要指定返回类型。如果不需要响应消息,方法可以声明为 void 返回类型。最常见的情况是使用返回类型来创建响应消息的有效载荷。然而,您还可以将其映射到响应消息的其他部分。本节描述了您可以在处理方法签名中使用的返回类型。spring-doc.cadn.net.cn

为了将返回值映射到响应消息的有效载荷,您需要使用 @ResponsePayload 注解对方法进行注释。此注解告诉 Spring-WS 返回值需要绑定到响应的有效载荷。spring-doc.cadn.net.cn

下表描述了支持的返回类型。它显示了支持的类型、参数是否应使用 @ResponsePayload 进行注解,以及任何其他注意事项。spring-doc.cadn.net.cn

姓名 支持的返回类型 @ResponsePayload 是必需的吗? 附加说明

无响应spring-doc.cadn.net.cn

voidspring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

TrAXspring-doc.cadn.net.cn

javax.xml.transform.Source 和子接口 (DOMSource, SAXSource, StreamSource, 和 StAXSource)spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

W3C DOMspring-doc.cadn.net.cn

org.w3c.dom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用spring-doc.cadn.net.cn

dom4jspring-doc.cadn.net.cn

org.dom4j.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 dom4j 在类路径中时启用。spring-doc.cadn.net.cn

JDOMspring-doc.cadn.net.cn

org.jdom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JDOM 在类路径中时启用。spring-doc.cadn.net.cn

XOMspring-doc.cadn.net.cn

nu.xom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 XOM 在类路径中时启用。spring-doc.cadn.net.cn

JAXB2spring-doc.cadn.net.cn

任何使用 javax.xml.bind.annotation.XmlRootElementjavax.xml.bind.JAXBElement 注解的类型。spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JAXB2 在类路径中时启用。spring-doc.cadn.net.cn

OXMspring-doc.cadn.net.cn

任何受 Spring OXM 支持的类型 Marshallerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当指定了 <sws:annotation-driven/>marshaller 属性时启用。spring-doc.cadn.net.cn

在定义处理方法签名时,有很多可能性。甚至可以扩展此机制以支持您自己的参数类型。请参阅DefaultMethodEndpointAdapterMethodReturnValueHandler 的类级别 Javadoc 以了解如何操作。spring-doc.cadn.net.cn

5.4. 端点映射

端点映射负责将传入的消息映射到适当的端点。某些端点映射默认是启用的,例如 PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping。然而,我们首先需要检查 EndpointMapping 的一般概念。spring-doc.cadn.net.cn

一个 EndpointMapping 提供了一个 EndpointInvocationChain,其中包含了与传入请求匹配的端点,也可能包含应用于请求和响应的端点拦截器列表。当请求到达时,MessageDispatcher 将其交给端点映射(endpoint mapping)以检查请求并找到合适的 EndpointInvocationChain。然后 MessageDispatcher 调用该端点以及链中的任何拦截器。spring-doc.cadn.net.cn

可配置的端点映射概念可以选择性地包含拦截器(这些拦截器可以操作请求、响应或同时操作两者),这一概念非常强大。许多支持功能可以构建到自定义的 EndpointMapping 实现中。例如,一个自定义的端点映射不仅可以根据消息的内容选择端点,还可以基于特定的 SOAP 头(或者甚至是多个 SOAP 头)进行选择。spring-doc.cadn.net.cn

大多数端点映射都继承自AbstractEndpointMapping,它提供了一个“interceptors”属性,即要使用的拦截器列表。EndpointInterceptors拦截请求——EndpointInterceptor接口中进行了讨论。此外,还有defaultEndpoint,当此端点映射未匹配到端点时使用的默认端点。spring-doc.cadn.net.cn

正如在端点中所解释的,@Endpoint风格允许您在一个端点类中处理多个请求。这是MethodEndpointMapping的责任。此映射确定了针对传入请求消息要调用哪个方法。spring-doc.cadn.net.cn

有两种端点映射可以将请求定向到方法:即 PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping。您可以通过在应用上下文中使用 <sws:annotation-driven/> 来启用这两种方法。spring-doc.cadn.net.cn

PayloadRootAnnotationMethodEndpointMapping 使用 @PayloadRoot 注解,并结合 localPartnamespace 元素,来标记具有特定限定名称的方法。每当具有此限定名称的消息作为有效负载的根元素传入时,该方法将被调用。例如,参见 上文spring-doc.cadn.net.cn

或者,SoapActionAnnotationMethodEndpointMapping 使用 @SoapAction 注解来标记具有特定 SOAP Action 的方法。每当带有此 SOAPAction 标头的消息传入时,该方法就会被调用。spring-doc.cadn.net.cn

5.4.1. WS-Addressing

WS-Addressing 指定了一个与传输无关的路由机制。它基于 ToAction SOAP 头,分别指示 SOAP 消息的目标和意图。此外,WS-Addressing 允许您定义一个返回地址(用于普通消息和错误消息)以及一个唯一的消息标识符,该标识符可用于关联。有关 WS-Addressing 的更多信息,请参见 https://en.wikipedia.org/wiki/WS-Addressing。以下示例展示了一个 WS-Addressing 消息:spring-doc.cadn.net.cn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsa="http://www.w3.org/2005/08/addressing">
  <SOAP-ENV::Header>
    <wsa:MessageID>urn:uuid:21363e0d-2645-4eb7-8afd-2f5ee1bb25cf</wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>http://example.com/business/client1</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To S:mustUnderstand="true">http://example/com/fabrikam</wsa:To>
    <wsa:Action>http://example.com/fabrikam/mail/Delete</wsa:Action>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <f:Delete xmlns:f="http://example.com/fabrikam">
      <f:maxCount>42</f:maxCount>
    </f:Delete>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

在前面的示例中,目标被设置为 http://example/com/fabrikam,而动作被设置为 http://example.com/fabrikam/mail/Delete。此外,还包含一个消息标识符和一个回复地址。默认情况下,该地址是“匿名”地址,表示响应应该使用与请求相同的通道发送(即 HTTP 响应),但也可以是另一个地址,如本示例所示。spring-doc.cadn.net.cn

在 Spring Web Services 中,WS-Addressing 被实现为一个端点映射。通过使用此映射,您可以将 WS-Addressing 操作与端点关联起来,类似于之前描述的 SoapActionAnnotationMethodEndpointMappingspring-doc.cadn.net.cn

使用AnnotationActionEndpointMapping

AnnotationActionEndpointMappingSoapActionAnnotationMethodEndpointMapping 类似,但使用 WS-Addressing 标头而不是 SOAP Action 传输标头。spring-doc.cadn.net.cn

要使用AnnotationActionEndpointMapping,请用@Action注解标记处理方法,类似于@Endpoint处理方法端点映射中描述的@PayloadRoot@SoapAction注解。以下示例展示了如何实现:spring-doc.cadn.net.cn

package samples;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.soap.addressing.server.annotation.Action

@Endpoint
public class AnnotationOrderEndpoint {
    private final OrderService orderService;

    public AnnotationOrderEndpoint(OrderService orderService) {
        this.orderService = orderService;
    }

    @Action("http://samples/RequestOrder")
    public Order getOrder(OrderRequest orderRequest) {
        return orderService.getOrder(orderRequest.getId());
    }

    @Action("http://samples/CreateOrder")
    public void order(Order order) {
        orderService.createOrder(order);
    }

}

前面的映射将具有 WS-Addressing Actionhttp://samples/RequestOrder 的请求路由到 getOrder 方法。具有 http://samples/CreateOrder 的请求被路由到 order 方法。spring-doc.cadn.net.cn

默认情况下,AnnotationActionEndpointMapping 支持 1.0(2006 年 5 月)和 2004 年 8 月版的 WS-Addressing。这两个版本最为流行,并且与 Axis 1 和 2、JAX-WS、XFire、Windows Communication Foundation (WCF) 以及 Windows Services Enhancements (WSE) 3.0 兼容。如有必要,可以将特定版本的规范注入到 versions 属性中。spring-doc.cadn.net.cn

除了使用 @Action 注解之外,您还可以使用 @Address 注解对类进行注解。如果设置了该值,它将与传入消息的 To 头部属性进行比较。spring-doc.cadn.net.cn

最后,有一个必需的messageSenders属性,用于向非匿名的、带外地址发送响应消息。您可以在此属性中设置MessageSender实现,这与您在WebServiceTemplate上的操作相同。请参阅URI和传输spring-doc.cadn.net.cn

5.4.2. 拦截请求 —— EndpointInterceptor接口

端点映射机制具有端点拦截器的概念。当你希望对某些请求应用特定功能时,这些拦截器非常有用——例如,处理与安全相关的 SOAP 头部信息或记录请求和响应消息。spring-doc.cadn.net.cn

Endpoint interceptors are typically defined by using a <sws:interceptors> element in your application context. In this element, you can define endpoint interceptor beans that apply to all endpoints defined in that application context. Alternatively, you can use <sws:payloadRoot> or <sws:soapAction> elements to specify for which payload root name or SOAP action the interceptor should apply. The following example shows how to do so:spring-doc.cadn.net.cn

<sws:interceptors>
  <bean class="samples.MyGlobalInterceptor"/>
  <sws:payloadRoot namespaceUri="http://www.example.com">
    <bean class="samples.MyPayloadRootInterceptor"/>
  </sws:payloadRoot>
  <sws:soapAction value="http://www.example.com/SoapAction">
    <bean class="samples.MySoapActionInterceptor1"/>
    <ref bean="mySoapActionInterceptor2"/>
  </sws:soapAction>
</sws:interceptors>

<bean id="mySoapActionInterceptor2" class="samples.MySoapActionInterceptor2"/>

在前面的示例中,我们定义了一个“全局”拦截器(MyGlobalInterceptor),它拦截所有请求和响应。我们还定义了一个仅适用于以http://www.example.com作为有效负载根命名空间的XML消息的拦截器。除了namespaceUri之外,我们还可以定义一个localPart属性,以进一步限制拦截器适用的消息范围。最后,我们定义了两个适用于消息具有http://www.example.com/SoapAction SOAP操作时的拦截器。请注意,第二个拦截器实际上是对<interceptors>元素外部的bean定义的引用。您可以在<interceptors>元素内的任何位置使用bean引用。spring-doc.cadn.net.cn

当您使用 @Configuration 类时,可以通过继承 WsConfigurerAdapter 来添加拦截器:spring-doc.cadn.net.cn

@Configuration
@EnableWs
public class MyWsConfiguration extends WsConfigurerAdapter {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyPayloadRootInterceptor());
  }

}

拦截器必须实现来自org.springframework.ws.server包的EndpointInterceptor接口。该接口定义了三个方法,一个可以在处理实际端点之前用于处理请求消息,一个可以用于处理正常的响应消息,另一个可以用于处理错误消息。后两个方法是在端点处理之后调用的。这三个方法应该提供了足够的灵活性来进行各种前置和后置处理。spring-doc.cadn.net.cn

拦截器中的 handleRequest(..) 方法返回一个布尔值。您可以使用此方法中断或继续调用链的处理。当此方法返回 true 时,端点处理链将继续。当它返回 false 时,MessageDispatcher 会解释为该拦截器自身已处理好相关逻辑,并且不再继续处理调用链中的其他拦截器和实际端点。 handleResponse(..)handleFault(..) 方法也有一个布尔返回值。当这些方法返回 false 时,响应将不会发送回客户端。spring-doc.cadn.net.cn

您的 Web 服务中可以使用多种标准的 EndpointInterceptor 实现。此外,还有 XwsSecurityInterceptor,它在 XwsSecurityInterceptor 中进行了描述。spring-doc.cadn.net.cn

PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor

当开发一个Web服务时,记录传入和传出的XML消息可能会很有用。Spring WS通过PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor类提供了这一功能。前者仅将消息的有效内容记录到Commons Logging日志中,后者则记录整个SOAP信封,包括SOAP头。以下示例展示了如何在端点映射中定义PayloadLoggingInterceptorspring-doc.cadn.net.cn

  <sws:interceptors>
    <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
  </sws:interceptors>

这两个拦截器都有两个属性,logRequestlogResponse,可以将它们设置为 false 以禁用对请求或响应消息的日志记录。spring-doc.cadn.net.cn

你可以使用之前描述的 WsConfigurerAdapter 方法,同样适用于 PayloadLoggingInterceptorspring-doc.cadn.net.cn

PayloadValidatingInterceptor

使用契约优先开发风格的好处之一是,我们可以使用模式来验证传入和传出的 XML 消息。Spring-WS 通过 PayloadValidatingInterceptor 提供了这一功能。此拦截器需要引用一个或多个 W3C XML 或 RELAX NG 模式,并且可以设置为验证请求、响应或两者。spring-doc.cadn.net.cn

请注意,请求验证听起来可能是个好主意,但它会使生成的 Web 服务非常严格。通常,请求是否通过验证并不重要,重要的是端点能否获取足够的信息来满足请求。验证响应是一个好主意,因为端点应遵循其模式。记住波斯特法则:<br> “对你所做的要保守,对你从他人那里接受的要宽松。”

以下示例使用了 PayloadValidatingInterceptor。在此示例中,我们使用 /WEB-INF/orders.xsd 中的模式来验证响应而非请求。请注意,通过设置 schemas 属性,PayloadValidatingInterceptor 也可以接受多个模式。spring-doc.cadn.net.cn

<bean id="validatingInterceptor"
        class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
    <property name="schema" value="/WEB-INF/orders.xsd"/>
    <property name="validateRequest" value="false"/>
    <property name="validateResponse" value="true"/>
</bean>

当然,您也可以像前面所描述的那样,对PayloadValidatingInterceptor采用WsConfigurerAdapter方法。spring-doc.cadn.net.cn

使用PayloadTransformingInterceptor

为了将有效负载转换为另一种XML格式,Spring Web Services 提供了 PayloadTransformingInterceptor。此端点拦截器基于XSLT样式表,特别适用于支持Web服务的多个版本,因为您可以将旧的消息格式转换为新的格式。以下示例使用了 PayloadTransformingInterceptorspring-doc.cadn.net.cn

<bean id="transformingInterceptor"
        class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor">
    <property name="requestXslt" value="/WEB-INF/oldRequests.xslt"/>
    <property name="responseXslt" value="/WEB-INF/oldResponses.xslt"/>
</bean>

在前面的示例中,我们通过使用/WEB-INF/oldRequests.xslt转换请求,并通过使用/WEB-INF/oldResponses.xslt转换响应消息。需要注意的是,由于端点拦截器是在端点映射级别注册的,因此您可以创建一个适用于“旧样式”消息的端点映射,并将拦截器添加到该映射中。因此,这种转换仅适用于这些“旧样式”消息。spring-doc.cadn.net.cn

你可以使用之前描述的 WsConfigurerAdapter 方法,同样适用于 PayloadTransformingInterceptorspring-doc.cadn.net.cn

5.5. 处理异常

Spring-WS 提供 EndpointExceptionResolvers 来减轻在消息被与请求匹配的端点处理时发生的意外异常所带来的痛苦。端点异常解析器在某种程度上类似于可以在 Web 应用程序描述符 web.xml 中定义的异常映射。然而,它们提供了更灵活的方式来处理异常。它们提供了有关在抛出异常时调用了哪个端点的信息。此外,通过编程的方式处理异常为您提供了更多关于如何适当响应的选择。您可以通过返回具有特定故障代码和字符串的 SOAP 错误来以任何您希望的方式处理异常,而不是通过暴露异常和堆栈跟踪来揭示应用程序的内部结构。spring-doc.cadn.net.cn

Endpoint exception resolvers are automatically picked up by the MessageDispatcher, so no explicit configuration is necessary.spring-doc.cadn.net.cn

除了实现EndpointExceptionResolver接口(这仅仅需要实现resolveException(MessageContext, endpoint, Exception)方法)之外,您还可以使用提供的实现之一。最简单的实现是SimpleSoapExceptionResolver,它会创建一个SOAP 1.1服务器或SOAP 1.2接收错误,并将异常消息用作错误字符串。SimpleSoapExceptionResolver是默认实现,但可以通过显式添加另一个解析器来覆盖。spring-doc.cadn.net.cn

5.5.1. SoapFaultMappingExceptionResolver

SoapFaultMappingExceptionResolver 是一种更复杂的实现。此解析器允许您获取可能抛出的任何异常的类名,并将其映射到 SOAP Fault:spring-doc.cadn.net.cn

<beans>
    <bean id="exceptionResolver"
        class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
        <property name="defaultFault" value="SERVER"/>
        <property name="exceptionMappings">
            <value>
                org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
            </value>
        </property>
    </bean>
</beans>

关键值和默认端点使用 faultCode,faultString,locale 的格式,其中只需要故障代码。如果未设置故障字符串,则默认为异常消息。如果未设置语言,则默认为英语。前面的配置将类型为 ValidationFailureException 的异常映射到客户端 SOAP 错误,错误字符串为 Invalid request,如下所示:spring-doc.cadn.net.cn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Client</faultcode>
           <faultstring>Invalid request</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

如果发生任何其他异常,则返回默认错误:一个以异常消息作为错误字符串的服务器端错误。spring-doc.cadn.net.cn

5.5.2. 使用 SoapFaultAnnotationExceptionResolver

你还可以使用 @SoapFault 注解来标注异常类,以指示每当抛出该异常时应返回的 SOAP 错误。为了让这些注解生效,你需要将 SoapFaultAnnotationExceptionResolver 添加到你的应用程序上下文中。该注解的元素包括错误代码枚举、错误字符串或原因以及语言。以下示例展示了这样的异常:spring-doc.cadn.net.cn

package samples;

import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;

@SoapFault(faultCode = FaultCode.SERVER)
public class MyBusinessException extends Exception {

    public MyClientException(String message) {
        super(message);
    }
}

每当在端点调用期间使用构造函数字符串 "Oops!" 抛出 MyBusinessException 时,会导致以下响应:spring-doc.cadn.net.cn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Server</faultcode>
           <faultstring>Oops!</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

5.6. 服务端测试

当涉及到测试您的Web服务端点时,您有两种可能的方法:spring-doc.cadn.net.cn

  • 编写单元测试,其中你为你的端点提供(模拟)参数以供使用。spring-doc.cadn.net.cn

    这种方法的优点是它非常容易实现(尤其是对于使用 @Endpoint 注解的类)。缺点是你并没有真正测试通过网络发送的 XML 消息的确切内容。spring-doc.cadn.net.cn

  • 编写集成测试,以测试消息的内容。spring-doc.cadn.net.cn

第一种方法可以轻松地通过使用诸如 EasyMock、JMock 等模拟框架来实现。下一节重点介绍如何使用 Spring Web Services 2.0 中引入的测试功能编写集成测试。spring-doc.cadn.net.cn

5.6.1. 编写服务器端集成测试

Spring Web Services 2.0 引入了对创建端点集成测试的支持。在此上下文中,端点是指处理(SOAP)消息的类(参见 端点)。spring-doc.cadn.net.cn

集成测试支持位于org.springframework.ws.test.server包中。该包中的核心类是MockWebServiceClient。其基本思想是,此客户端创建一个请求消息,然后将其发送到在标准MessageDispatcherServlet应用程序上下文中配置的端点(参见MessageDispatcherServlet)。这些端点处理消息并创建响应。然后客户端接收此响应并根据注册的期望值对其进行验证。spring-doc.cadn.net.cn

典型用法MockWebServiceClient是:。spring-doc.cadn.net.cn

  1. 通过调用MockWebServiceClient.createClient(ApplicationContext)MockWebServiceClient.createClient(WebServiceMessageReceiver, WebServiceMessageFactory)创建一个MockWebServiceClient实例。spring-doc.cadn.net.cn

  2. 通过调用 sendRequest(RequestCreator) 发送请求消息,可以使用 RequestCreators 中提供的默认 RequestCreator 实现(可以静态导入)。spring-doc.cadn.net.cn

  3. 通过调用 andExpect(ResponseMatcher) 来设置响应期望,可以使用 ResponseMatchers 中提供的默认 ResponseMatcher 实现(可以静态导入)。通过链式调用 andExpect(ResponseMatcher) 可以设置多个期望。spring-doc.cadn.net.cn

请注意,MockWebServiceClient(及相关类)提供了“流畅”的 API,因此您可以通常使用 IDE 中的代码补全功能来引导您完成设置模拟服务器的过程。
同时请注意,您可以依赖 Spring Web Services 中提供的标准日志记录功能来进行单元测试。有时,检查请求或响应消息以找出某个测试失败的原因可能会很有用。有关更多信息,请参见消息日志记录和跟踪

例如,考虑以下 web 服务端点类:spring-doc.cadn.net.cn

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint                                                                (1)
public class CustomerEndpoint {

  @ResponsePayload                                                       (2)
  public CustomerCountResponse getCustomerCount(                         (2)
      @RequestPayload CustomerCountRequest request) {                    (2)
    CustomerCountResponse response = new CustomerCountResponse();
    response.setCustomerCount(10);
    return response;
  }

}
1 @Endpoint 注解的 CustomerEndpoint。参见 端点
2 getCustomerCount() 方法接受一个 CustomerCountRequest 作为参数,并返回一个 CustomerCountResponse。这两个类都是 marshaller 支持的对象。例如,它们可以具有 @XmlRootElement 注解以被 JAXB2 支持。

以下示例显示了针对 CustomerEndpoint 的典型测试:spring-doc.cadn.net.cn

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.ws.test.server.MockWebServiceClient;                       (1)
import static org.springframework.ws.test.server.RequestCreators.*;                   (1)
import static org.springframework.ws.test.server.ResponseMatchers.*;                  (1)

@RunWith(SpringJUnit4ClassRunner.class)                                               (2)
@ContextConfiguration("spring-ws-servlet.xml")                                        (2)
public class CustomerEndpointIntegrationTest {

  @Autowired
  private ApplicationContext applicationContext;                                      (3)

  private MockWebServiceClient mockClient;

  @Before
  public void createClient() {
    mockClient = MockWebServiceClient.createClient(applicationContext);               (4)
  }

  @Test
  public void customerEndpoint() throws Exception {
    Source requestPayload = new StringSource(
      "<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
        "<customerName>John Doe</customerName>" +
      "</customerCountRequest>");
    Source responsePayload = new StringSource(
      "<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
        "<customerCount>10</customerCount>" +
      "</customerCountResponse>");

    mockClient.sendRequest(withPayload(requestPayload)).                              (5)
      andExpect(payload(responsePayload));                                            (5)
  }
}
1 CustomerEndpointIntegrationTest 导入了 MockWebServiceClient 并静态导入了 RequestCreatorsResponseMatchers
2 此测试使用 Spring 框架中提供的标准测试工具。这不是必需的,但通常是最简单的设置测试的方法。
3 应用上下文是一个标准的 Spring-WS 应用上下文(参见 MessageDispatcherServlet),从 spring-ws-servlet.xml 中读取。在此情况下,应用上下文包含一个针对 CustomerEndpoint 的 bean 定义(或者可能使用了 <context:component-scan />)。
4 @Before方法中,我们通过使用createClient工厂方法创建了一个MockWebServiceClient
5 我们通过调用sendRequest()并使用静态导入的RequestCreators所提供的withPayload()RequestCreator来发送请求(参见使用RequestCreatorRequestCreators)。

我们还通过调用andExpect()并使用静态导入的ResponseMatchers所提供的payload()ResponseMatcher来设置响应期望(参见使用ResponseMatcherResponseMatchers)。spring-doc.cadn.net.cn

这部分测试可能看起来有点令人困惑,但您的IDE的代码补全功能会提供很大帮助。在输入sendRequest(后,只要您静态导入了RequestCreators,您的IDE就可以为您提供一系列可能的请求创建策略。同样的规则也适用于andExpect(),前提是您静态导入了ResponseMatchersspring-doc.cadn.net.cn

5.6.2. 使用 RequestCreatorRequestCreators

最初,MockWebServiceClient需要为端点创建一个请求消息以供消费。客户端为此目的使用RequestCreator策略接口:spring-doc.cadn.net.cn

public interface RequestCreator {

  WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
    throws IOException;

}

你可以自己编写此接口的实现,通过使用消息工厂来创建请求消息,但你绝对不是必须这样做。RequestCreators 类提供了一种方法,可以在 withPayload() 方法中根据给定的有效负载创建一个 RequestCreator。你通常会静态导入 RequestCreatorsspring-doc.cadn.net.cn

5.6.3. 使用 ResponseMatcherResponseMatchers

当请求消息已被端点处理并收到响应时,MockWebServiceClient 可以验证此响应消息是否符合某些预期。客户端为此目的使用 ResponseMatcher 策略接口:spring-doc.cadn.net.cn

public interface ResponseMatcher {

    void match(WebServiceMessage request,
               WebServiceMessage response)
      throws IOException, AssertionError;

}

再次强调,你可以编写自己的接口实现,在消息不符合预期时抛出 AssertionError 实例,但你完全不必这样做,因为 ResponseMatchers 类为你提供了标准的 ResponseMatcher 实现,供你在测试中使用。你通常会静态导入这个类。spring-doc.cadn.net.cn

ResponseMatchers 类提供了以下响应匹配器:spring-doc.cadn.net.cn

ResponseMatchers 方法 描述

payload()spring-doc.cadn.net.cn

期望得到给定的响应内容。spring-doc.cadn.net.cn

validPayload()spring-doc.cadn.net.cn

期望响应内容根据给定的 XSD 模式进行验证。spring-doc.cadn.net.cn

xpath()spring-doc.cadn.net.cn

期望某个给定的 XPath 表达式存在、不存在或评估为给定值。spring-doc.cadn.net.cn

soapHeader()spring-doc.cadn.net.cn

期望在响应消息中存在给定的 SOAP 头。spring-doc.cadn.net.cn

noFault()spring-doc.cadn.net.cn

期望响应消息不包含 SOAP 错误。spring-doc.cadn.net.cn

mustUnderstandFault()clientOrSenderFault()serverOrReceiverFault()versionMismatchFault()spring-doc.cadn.net.cn

期望响应消息包含特定的 SOAP 错误。spring-doc.cadn.net.cn

您可以通过链接 andExpect() 调用来设置多个响应期望:spring-doc.cadn.net.cn

mockClient.sendRequest(...).
 andExpect(payload(expectedResponsePayload)).
 andExpect(validPayload(schemaResource));

有关 ResponseMatchers 提供的响应匹配器的更多信息,请参见 Javadocspring-doc.cadn.net.cn

6. 在客户端使用 Spring Web Services

Spring-WS 提供了一个客户端的 Web 服务 API,允许以一致的、基于 XML 的方式访问 Web 服务。它还支持使用编组器和解组器,以便您的服务层代码可以完全专注于处理 Java 对象。spring-doc.cadn.net.cn

The org.springframework.ws.client.core 包提供了使用客户端访问 API 的核心功能。它包含简化 Web 服务使用的模板类,类似于核心 Spring JdbcTemplate 对 JDBC 所做的工作。Spring 模板类的共同设计原则是提供用于执行常见操作的帮助方法,并且对于更复杂的用法,委托给用户实现的回调接口。Web 服务模板遵循相同的设计。这些类提供了各种便捷方法用于spring-doc.cadn.net.cn

6.1. 使用客户端 API

本节描述如何使用客户端 API。有关如何使用服务器端 API,请参见使用 Spring-WS 创建 Web 服务spring-doc.cadn.net.cn

6.1.1. WebServiceTemplate

WebServiceTemplate 是 Spring-WS 中用于客户端 Web 服务访问的核心类。它包含发送 Source 对象的方法,并可以将响应消息作为 SourceResult 接收。此外,它还可以在通过传输发送之前将对象编组为 XML,并将任何响应 XML 反编组为对象。spring-doc.cadn.net.cn

URIs 和传输

WebServiceTemplate 类使用 URI 作为消息目的地。您可以直接在模板本身上设置 defaultUri 属性,或者在调用模板上的方法时显式提供一个 URI。URI 会被解析为一个 WebServiceMessageSender,它负责通过传输层发送 XML 消息。您可以通过使用 WebServiceTemplate 类的 messageSendermessageSenders 属性来设置一个或多个消息发送器。spring-doc.cadn.net.cn

HTTP 传输

对于通过 HTTP 发送消息,WebServiceMessageSender 接口有两种实现。默认实现是 HttpUrlConnectionMessageSender,它使用 Java 本身提供的功能。另一种是 HttpComponentsMessageSender,它使用 Apache HttpComponents HttpClient。如果你需要更高级且易于使用的功能(例如身份验证、HTTP 连接池等),请使用后者。spring-doc.cadn.net.cn

要使用 HTTP 传输,要么将 defaultUri 设置为类似 http://example.com/services 的值,要么为某个方法提供 uri 参数。spring-doc.cadn.net.cn

以下示例显示了如何为 HTTP 传输使用默认配置:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="defaultUri" value="http://example.com/WebService"/>
    </bean>

</beans>

以下示例展示了如何覆盖默认配置,以及如何使用 Apache HttpClient 通过 HTTP 认证进行身份验证:spring-doc.cadn.net.cn

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
    <constructor-arg ref="messageFactory"/>
    <property name="messageSender">
        <bean class="org.springframework.ws.transport.http.HttpComponentsMessageSender">
            <property name="credentials">
                <bean class="org.apache.http.auth.UsernamePasswordCredentials">
                    <constructor-arg value="john:secret"/>
                </bean>
            </property>
        </bean>
    </property>
    <property name="defaultUri" value="http://example.com/WebService"/>
</bean>
JMS 传输

为了通过JMS发送消息,Spring Web Services提供了JmsMessageSender。此类使用Spring框架的功能将WebServiceMessage转换为JMS的Message,并将其通过QueueTopic发送出去,同时接收响应(如果有的话)。spring-doc.cadn.net.cn

要使用JmsMessageSender,您需要将defaultUriuri参数设置为JMS URI,该URI至少由jms:前缀和目标名称组成。一些JMS URI的示例包括:jms:SomeQueuejms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENTjms:RequestQueue?replyToName=ResponseName。有关此URI语法的更多信息,请参阅JmsMessageSender的Javadocspring-doc.cadn.net.cn

默认情况下,JmsMessageSender 发送 JMS BytesMessage,但您可以通过在 JMS URI 上使用 messageType 参数来覆盖此行为以使用 TextMessages,例如:jms:Queue?messageType=TEXT_MESSAGE。请注意,BytesMessages 是首选类型,因为 TextMessages 不能可靠地支持附件和字符编码。spring-doc.cadn.net.cn

以下示例显示了如何将 JMS 传输与 ActiveMQ 连接工厂结合使用:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
    </bean>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.jms.JmsMessageSender">
                <property name="connectionFactory" ref="connectionFactory"/>
            </bean>
        </property>
        <property name="defaultUri" value="jms:RequestQueue?deliveryMode=NON_PERSISTENT"/>
    </bean>

</beans>
邮件传输

Spring Web Services 还提供了一个电子邮件传输功能,您可以使用它通过 SMTP 发送 Web 服务消息,并通过 POP3 或 IMAP 检索它们。客户端的电子邮件功能包含在 MailMessageSender 类中。该类从请求 WebServiceMessage 创建电子邮件消息并通过 SMTP 发送。然后等待响应消息到达传入的 POP3 或 IMAP 服务器。spring-doc.cadn.net.cn

要使用MailMessageSender,请将defaultUriuri参数设置为mailto URI — 例如,mailto:[email protected]mailto:server@localhost?subject=SOAP%20Test。确保消息发送器已正确配置了transportUri,它指示用于发送请求的服务器(通常为SMTP服务器),以及storeUri,它指示用于轮询响应的服务器(通常为POP3或IMAP服务器)。spring-doc.cadn.net.cn

以下示例展示了如何使用电子邮件传输:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.mail.MailMessageSender">
                <property name="from" value="Spring-WS SOAP Client &lt;[email protected]&gt;"/>
                <property name="transportUri" value="smtp://client:[email protected]"/>
                <property name="storeUri" value="imap://client:[email protected]/INBOX"/>
            </bean>
        </property>
        <property name="defaultUri" value="mailto:[email protected]?subject=SOAP%20Test"/>
    </bean>

</beans>
XMPP 传输

Spring Web Services 2.0 引入了 XMPP(Jabber)传输,您可以使用它通过 XMPP 发送和接收 Web 服务消息。客户端的 XMPP 功能包含在 XmppMessageSender 类中。此类从请求 WebServiceMessage 创建一个 XMPP 消息并通过 XMPP 发送。然后它会监听响应消息的到来。spring-doc.cadn.net.cn

要使用XmppMessageSender,请将defaultUriuri参数设置为xmpp URI — 例如,xmpp:[email protected]。发送方还需要一个XMPPConnection才能工作,可以方便地通过使用org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean来创建。spring-doc.cadn.net.cn

以下示例展示了如何使用 XMPP 传输:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.xmpp.XmppMessageSender">
                <property name="connection" ref="connection"/>
            </bean>
        </property>
        <property name="defaultUri" value="xmpp:[email protected]"/>
    </bean>

</beans>
消息工厂

除了消息发送器之外,WebServiceTemplate 还需要一个 Web 服务消息工厂。SOAP 有两种消息工厂:SaajSoapMessageFactoryAxiomSoapMessageFactory。如果没有指定消息工厂(通过设置 messageFactory 属性),Spring-WS 默认会使用 SaajSoapMessageFactoryspring-doc.cadn.net.cn

6.1.2. 发送和接收WebServiceMessage

WebServiceTemplate 包含许多用于发送和接收 Web 服务消息的便捷方法。其中一些方法接受并返回 Source,另一些方法则返回 Result。此外,还有一些方法可以将对象编组和解组为 XML。以下示例向 Web 服务发送了一条简单的 XML 消息:spring-doc.cadn.net.cn

import java.io.StringReader;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;

public class WebServiceClient {

    private static final String MESSAGE =
        "<message xmlns=\"http://tempuri.org\">Hello, Web Service World</message>";

    private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();

    public void setDefaultUri(String defaultUri) {
        webServiceTemplate.setDefaultUri(defaultUri);
    }

    // send to the configured default URI
    public void simpleSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult(source, result);
    }

    // send to an explicit URI
    public void customSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult("http://localhost:8080/AnotherWebService",
            source, result);
    }

}
<beans xmlns="http://www.springframework.org/schema/beans">

    <bean id="webServiceClient" class="WebServiceClient">
        <property name="defaultUri" value="http://localhost:8080/WebService"/>
    </bean>

</beans>

前面的示例使用WebServiceTemplate向位于http://localhost:8080/WebService的Web服务发送“Hello, World”消息(在simpleSendAndReceive()方法的情况下),并将结果写入控制台。WebServiceTemplate被注入默认的URI,之所以使用默认URI是因为Java代码中没有显式提供URI。spring-doc.cadn.net.cn

请注意,WebServiceTemplate 类在配置完成后是线程安全的(假设其所有依赖项也是线程安全的,Spring-WS 提供的所有依赖项均满足此条件),因此多个对象可以共享同一个 WebServiceTemplate 实例。WebServiceTemplate 暴露了一个无参构造函数以及 messageFactorymessageSender 两个 bean 属性,您可以使用它们来构造实例(通过 Spring 容器或普通的 Java 代码)。此外,您还可以考虑继承 Spring-WS 的 WebServiceGatewaySupport 便捷基类,该类暴露了方便的 bean 属性以简化配置。(您不必扩展这个基类。它仅作为便利类提供。)spring-doc.cadn.net.cn

6.1.3. 发送和接收POJO — 编组与解组

为了方便发送普通的 Java 对象,WebServiceTemplate 提供了许多 send(..) 方法,这些方法将 Object 作为消息数据内容的参数。类 WebServiceTemplate 中的 marshalSendAndReceive(..) 方法将请求对象转换为 XML 的任务委托给一个 Marshaller,并将响应 XML 转换为对象的任务委托给一个 Unmarshaller。(有关编组和解组的更多信息,请参阅 Spring 框架参考文档。)通过使用编组器,您的应用程序代码可以专注于正在发送或接收的业务对象,而无需关心它如何以 XML 形式表示。要使用编组功能,您需要通过 WebServiceTemplate 类的 marshallerunmarshaller 属性设置一个编组器和一个解组器。spring-doc.cadn.net.cn

6.1.4. 使用 WebServiceMessageCallback

为了适应在消息上设置 SOAP 头部以及其他配置,WebServiceMessageCallback 接口允许您在消息创建之后、发送之前访问该消息。以下示例演示了如何在通过编组对象创建的消息上设置 SOAP 操作头部:spring-doc.cadn.net.cn

public void marshalWithSoapActionHeader(MyObject o) {

    webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() {

        public void doWithMessage(WebServiceMessage message) {
            ((SoapMessage)message).setSoapAction("http://tempuri.org/Action");
        }
    });
}
请注意,您还可以使用 org.springframework.ws.soap.client.core.SoapActionCallback 来设置 SOAP 操作头。
WS-Addressing

除了对服务器端 WS-Addressing的支持外,Spring Web Services 在客户端也对该规范提供了支持。spring-doc.cadn.net.cn

为了在客户端设置 WS-Addressing 头信息,您可以使用 org.springframework.ws.soap.addressing.client.ActionCallback。此回调将所需的操作头作为参数。它还具有用于指定 WS-Addressing 版本和 To 头的构造函数。如果未指定,To 头将默认为正在建立的连接的 URL。spring-doc.cadn.net.cn

以下示例将Action标头设置为http://samples/RequestOrderspring-doc.cadn.net.cn

webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));

6.1.5. 使用 WebServiceMessageExtractor

The WebServiceMessageExtractor 接口是一个低级别的回调接口,您可以完全控制从接收到的 WebServiceMessage 中提取 Object 的过程。 WebServiceTemplate 在底层连接到服务资源仍然打开时调用提供的 WebServiceMessageExtractor 上的 extractData(..) 方法。以下示例展示了 WebServiceMessageExtractor 的实际应用:spring-doc.cadn.net.cn

public void marshalWithSoapActionHeader(final Source s) {
    final Transformer transformer = transformerFactory.newTransformer();
    webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
        public void doWithMessage(WebServiceMessage message) {
            transformer.transform(s, message.getPayloadResult());
        },
        new WebServiceMessageExtractor() {
            public Object extractData(WebServiceMessage message) throws IOException {
                // do your own transforms with message.getPayloadResult()
                //     or message.getPayloadSource()
            }
          }
        });
}

6.2. 客户端测试

当涉及到测试您的Web服务客户端(即,使用WebServiceTemplate访问Web服务的类)时,您有两种可能的方法:spring-doc.cadn.net.cn

  • 编写单元测试,模拟掉 WebServiceTemplate 类、WebServiceOperations 接口或整个客户端类。spring-doc.cadn.net.cn

    这种方法的优点是容易实现。缺点是你并没有真正测试通过网络发送的XML消息的确切内容,特别是当整个客户端类都被模拟出来时。spring-doc.cadn.net.cn

  • 编写集成测试,以测试消息的内容。spring-doc.cadn.net.cn

第一种方法可以轻松地通过使用诸如 EasyMock、JMock 等模拟框架来实现。下一节将重点介绍如何使用 Spring Web Services 2.0 中引入的测试功能编写集成测试。spring-doc.cadn.net.cn

6.2.1. 编写客户端集成测试

Spring Web Services 2.0 引入了对创建 Web 服务客户端集成测试的支持。在此上下文中,客户端是一个使用 WebServiceTemplate 访问 Web 服务的类。spring-doc.cadn.net.cn

集成测试支持位于org.springframework.ws.test.client包中。该包中的核心类是MockWebServiceServer。其基本思想是,Web服务模板连接到此模拟服务器并向其发送请求消息,然后模拟服务器根据注册的期望验证该请求消息。如果满足期望,模拟服务器将准备响应消息并将其发送回模板。spring-doc.cadn.net.cn

典型用法MockWebServiceServer是:。spring-doc.cadn.net.cn

  1. 通过调用 MockWebServiceServer.createServer(WebServiceTemplate)MockWebServiceServer.createServer(WebServiceGatewaySupport)MockWebServiceServer.createServer(ApplicationContext) 创建一个 MockWebServiceServer 实例。spring-doc.cadn.net.cn

  2. 通过调用 expect(RequestMatcher) 来设置请求期望,或者使用 RequestMatchers 中提供的默认 RequestMatcher 实现(可以静态导入)。通过链式调用 andExpect(RequestMatcher) 可以设置多个期望。spring-doc.cadn.net.cn

  3. 通过调用 andRespond(ResponseCreator) 创建一个适当的响应消息,可能使用 ResponseCreators 中提供的默认 ResponseCreator 实现(可以静态导入)。spring-doc.cadn.net.cn

  4. 像平常一样使用WebServiceTemplate,可以直接使用或通过客户端代码使用。spring-doc.cadn.net.cn

  5. 调用 MockWebServiceServer.verify() 以确保所有预期都已满足。spring-doc.cadn.net.cn

请注意,MockWebServiceServer(及相关类)提供了“流畅”的 API,因此您可以通常使用 IDE 中的代码补全功能来引导您完成设置模拟服务器的过程。
同时请注意,您可以依赖 Spring Web Services 中提供的标准日志记录功能来进行单元测试。有时,检查请求或响应消息以找出某个测试失败的原因可能会很有用。有关更多信息,请参见消息日志记录和跟踪

例如,考虑以下 Web 服务客户端类:spring-doc.cadn.net.cn

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class CustomerClient extends WebServiceGatewaySupport {                          (1)

  public int getCustomerCount() {
    CustomerCountRequest request = new CustomerCountRequest();                          (2)
    request.setCustomerName("John Doe");

    CustomerCountResponse response =
      (CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request);   (3)

    return response.getCustomerCount();
  }

}
1 CustomerClient扩展了WebServiceGatewaySupport,这使其具有一个webServiceTemplate属性。
2 CustomerCountRequest 是一个由编组器支持的对象。例如,它可以具有 @XmlRootElement 注解以被 JAXB2 支持。
3 CustomerClient 使用 WebServiceGatewaySupport 提供的 WebServiceTemplate 将请求对象编组为 SOAP 消息,并将其发送到 Web 服务。响应对象被解组为 CustomerCountResponse

以下示例显示了针对 CustomerClient 的典型测试:spring-doc.cadn.net.cn

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

import org.springframework.ws.test.client.MockWebServiceServer;                         (1)
import static org.springframework.ws.test.client.RequestMatchers.*;                     (1)
import static org.springframework.ws.test.client.ResponseCreators.*;                    (1)

@RunWith(SpringJUnit4ClassRunner.class)                                                 (2)
@ContextConfiguration("integration-test.xml")                                           (2)
public class CustomerClientIntegrationTest {

  @Autowired
  private CustomerClient client;                                                        (3)

  private MockWebServiceServer mockServer;                                              (4)

  @Before
  public void createServer() throws Exception {
    mockServer = MockWebServiceServer.createServer(client);
  }

  @Test
  public void customerClient() throws Exception {
    Source requestPayload = new StringSource(
      "<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
        "<customerName>John Doe</customerName>" +
      "</customerCountRequest>");
    Source responsePayload = new StringSource(
      "<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
        "<customerCount>10</customerCount>" +
      "</customerCountResponse>");

    mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload));(5)

    int result = client.getCustomerCount();                                             (6)
    assertEquals(10, result);                                                           (6)

    mockServer.verify();                                                                (7)
  }

}
1 CustomerClientIntegrationTest 导入了 MockWebServiceServer 并静态导入了 RequestMatchersResponseCreators
2 此测试使用 Spring 框架中提供的标准测试工具。这不是必需的,但通常是最简单的设置测试的方法。
3 CustomerClient 配置在 integration-test.xml 中,并通过 @Autowired 装配到此测试中。
4 @Before方法中,我们通过使用createServer工厂方法创建了一个MockWebServiceServer
5 我们通过调用 expect() 并使用静态导入的 RequestMatchers 提供的 payload() RequestMatcher 来定义期望(参见 使用 RequestMatcherRequestMatchers)。

我们还通过调用 andRespond() 并使用静态导入的 ResponseCreators 提供的 withPayload() ResponseCreator 来设置响应(参见 使用 ResponseCreatorResponseCreators)。spring-doc.cadn.net.cn

这部分测试可能看起来有点令人困惑,但您的 IDE 的代码补全功能会提供很大帮助。在您输入 expect( 后,只要您静态导入了 RequestMatchers,您的 IDE 就可以为您提供可能的请求匹配策略列表。同样的规则也适用于 andRespond(,前提是您静态导入了 ResponseCreatorsspring-doc.cadn.net.cn

6 我们对CustomerClient调用getCustomerCount(),从而使用了WebServiceTemplate。此时模板已经设置为“测试模式”,因此该方法调用不会建立真实的(HTTP)连接。我们还根据方法调用的结果做了一些JUnit断言。
7 我们对MockWebServiceServer调用verify(),验证确实收到了预期的消息。

6.2.2. 使用 RequestMatcherRequestMatchers

为了验证请求消息是否符合某些预期,MockWebServiceServer 使用了 RequestMatcher 策略接口。此接口定义的契约如下:spring-doc.cadn.net.cn

public interface RequestMatcher {

  void match(URI uri,
             WebServiceMessage request)
    throws IOException,
           AssertionError;
}

你可以编写自己的接口实现,在消息不符合预期时抛出AssertionError异常,但你肯定不是必须这样做。RequestMatchers类为你提供了标准的RequestMatcher实现,供你在测试中使用。你通常会静态导入这个类。spring-doc.cadn.net.cn

RequestMatchers 类提供了以下请求匹配器:spring-doc.cadn.net.cn

RequestMatchers 方法 描述

anything()spring-doc.cadn.net.cn

期望任何形式的请求。spring-doc.cadn.net.cn

payload()spring-doc.cadn.net.cn

期望给定的请求负载。spring-doc.cadn.net.cn

validPayload()spring-doc.cadn.net.cn

期望请求负载根据给定的 XSD 模式进行验证。spring-doc.cadn.net.cn

xpath()spring-doc.cadn.net.cn

期望某个给定的 XPath 表达式存在、不存在或评估为给定值。spring-doc.cadn.net.cn

soapHeader()spring-doc.cadn.net.cn

期望请求消息中存在给定的 SOAP 头。spring-doc.cadn.net.cn

connectionTo()spring-doc.cadn.net.cn

期望连接到给定的URL。spring-doc.cadn.net.cn

你可以通过链式调用 andExpect() 来设置多个请求期望:spring-doc.cadn.net.cn

mockServer.expect(connectionTo("http://example.com")).
 andExpect(payload(expectedRequestPayload)).
 andExpect(validPayload(schemaResource)).
 andRespond(...);

有关 RequestMatchers 提供的请求匹配器的更多信息,请参见 Javadocspring-doc.cadn.net.cn

6.2.3. 使用 ResponseCreatorResponseCreators

当请求消息经过验证并符合定义的预期时,MockWebServiceServer 会为 WebServiceTemplate 创建一个响应消息以供其使用。服务器为此目的使用 ResponseCreator 策略接口:spring-doc.cadn.net.cn

public interface ResponseCreator {

  WebServiceMessage createResponse(URI uri,
                                   WebServiceMessage request,
                                   WebServiceMessageFactory messageFactory)
    throws IOException;

}

再次强调,您可以自行编写此接口的实现,通过使用消息工厂创建响应消息,但您完全不必这样做,因为 ResponseCreators 类为您提供了标准的 ResponseCreator 实现,您可以在测试中使用它们。通常情况下,您可以静态导入此类。spring-doc.cadn.net.cn

ResponseCreators 类提供了以下响应:spring-doc.cadn.net.cn

ResponseCreators 方法 描述

withPayload()spring-doc.cadn.net.cn

创建一个具有给定有效负载的响应消息。spring-doc.cadn.net.cn

withError()spring-doc.cadn.net.cn

在响应连接中创建一个错误。此方法为您提供测试错误处理的机会。spring-doc.cadn.net.cn

withException()spring-doc.cadn.net.cn

从响应连接读取时抛出异常。此方法为您提供测试异常处理的机会。spring-doc.cadn.net.cn

withMustUnderstandFault(), withClientOrSenderFault(), withServerOrReceiverFault(), or withVersionMismatchFault()spring-doc.cadn.net.cn

使用给定的 SOAP 错误创建响应消息。此方法为您提供测试错误处理的机会。spring-doc.cadn.net.cn

有关 RequestMatchers 提供的请求匹配器的更多信息,请参见 Javadocspring-doc.cadn.net.cn

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

本章解释如何为您的Web服务添加WS-Security相关内容。我们重点关注WS-Security的三个不同领域:spring-doc.cadn.net.cn

  • 认证: 这是确定主体是否是他们所声称的身份的过程。在此上下文中,“主体”通常指用户、设备或您应用程序中可以执行操作的其他系统。spring-doc.cadn.net.cn

  • 数字签名: 消息的数字签名是基于文档和签名者私钥的一段信息。它通过对哈希函数和私有签名函数(使用签名者的私钥加密)的运用来创建。spring-doc.cadn.net.cn

  • 加密与解密: 加密是将数据转换为在没有适当密钥的情况下无法读取的形式的过程。它主要用于对信息进行保密,防止未授权的人获取。解密是加密的逆过程,是将加密的数据转换回可读形式的过程。spring-doc.cadn.net.cn

这三个区域分别通过使用XwsSecurityInterceptorWss4jSecurityInterceptor来实现,我们在XwsSecurityInterceptor使用Wss4jSecurityInterceptor中分别描述了它们。spring-doc.cadn.net.cn

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

7.1. XwsSecurityInterceptor

XwsSecurityInterceptor 是一个 EndpointInterceptor(参见 拦截请求 —— EndpointInterceptor 接口),它基于 SUN 的 XML 和 Web 服务安全包 (XWSS)。此 WS-Security 实现是 Java Web 服务开发包 (Java WSDP) 的一部分。spring-doc.cadn.net.cn

像其他端点拦截器一样,它在端点映射中定义(参见端点映射)。这意味着您可以有选择地添加 WS-Security 支持。某些端点映射需要它,而其他则不需要。spring-doc.cadn.net.cn

请注意,XWSS 既需要 SUN 1.5 JDK,也需要 SUN SAAJ 参考实现。WSS4J 拦截器没有这些要求(参见 使用 Wss4jSecurityInterceptor)。

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

此外,安全拦截器需要一个或多个 CallbackHandler 实例才能运行。这些处理器用于检索证书、私钥、验证用户凭据等。Spring-WS 针对大多数常见的安全问题提供了处理器,例如,针对 Spring Security 认证管理器进行身份验证,以及基于 X509 证书对传出消息进行签名。以下部分说明了针对哪些安全问题使用哪个回调处理器。您可以通过使用 callbackHandlercallbackHandlers 属性来设置回调处理器。spring-doc.cadn.net.cn

以下示例展示了如何连接XwsSecurityInterceptorspring-doc.cadn.net.cn

<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文件进行配置的。它使用了在文件后面定义的两个回调处理程序。spring-doc.cadn.net.cn

7.1.1. 密钥库

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

java.security.KeyStore 类代表一个用于存储加密密钥和证书的设施。它可以包含三种不同类型的元素:spring-doc.cadn.net.cn

  • 私钥: 这些密钥用于自我身份验证。私钥附带一个对应公钥的证书链。在 WS-Security 领域中,这涉及到消息签名和消息解密。spring-doc.cadn.net.cn

  • 对称密钥: 对称(或秘密)密钥也用于消息的加密和解密——不同之处在于双方(发送方和接收方)共享相同的秘密密钥。spring-doc.cadn.net.cn

  • 受信任的证书:这些 X509 证书被称为“受信任的证书”,因为密钥库所有者相信证书中的公钥确实属于该证书的所有者。在 WS-Security 中,这些证书用于证书验证、签名验证和加密。spring-doc.cadn.net.cn

使用keytool

keytool 程序是一个密钥和证书管理工具,随您的 Java 虚拟机提供。您可以使用此工具创建新的密钥库,向其中添加新的私钥和证书等。提供 keytool 命令的完整参考超出了本文档的范围,但您可以在此处找到参考这里 或通过在命令行中使用 keytool -help 命令。spring-doc.cadn.net.cn

使用KeyStoreFactoryBean

为了通过使用 Spring 配置轻松加载密钥库,您可以使用 KeyStoreFactoryBean。它有一个资源位置属性,您可以将其设置为指向要加载的密钥库的路径。可以提供密码以检查密钥库数据的完整性。如果未提供密码,则不会执行完整性检查。以下清单配置了一个 KeyStoreFactoryBeanspring-doc.cadn.net.cn

<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 属性,将创建一个新的空密钥库,这很可能不是你想要的结果。
KeyStoreCallbackHandler

要在一个XwsSecurityInterceptor中使用密钥库,您需要定义一个KeyStoreCallbackHandler。该回调具有三个类型为keystore的属性:(keyStoretrustStoresymmetricStore)。处理器使用的具体存储取决于此处理器要执行的加密操作。对于私钥操作,使用keyStore。对于对称密钥操作,使用symmetricStore。对于确定信任关系,使用trustStore。下表对此进行了说明:spring-doc.cadn.net.cn

加密操作 使用的密钥库

证书验证spring-doc.cadn.net.cn

首先 keyStore,然后 trustStorespring-doc.cadn.net.cn

基于私钥的解密spring-doc.cadn.net.cn

keyStorespring-doc.cadn.net.cn

基于对称密钥的解密spring-doc.cadn.net.cn

symmetricStorespring-doc.cadn.net.cn

基于公钥证书的加密spring-doc.cadn.net.cn

trustStorespring-doc.cadn.net.cn

基于对称密钥的加密spring-doc.cadn.net.cn

symmetricStorespring-doc.cadn.net.cn

签名spring-doc.cadn.net.cn

keyStorespring-doc.cadn.net.cn

签名验证spring-doc.cadn.net.cn

trustStorespring-doc.cadn.net.cn

此外,KeyStoreCallbackHandler 具有 privateKeyPassword 属性,该属性应设置为解锁 `keyStore` 中包含的私钥。spring-doc.cadn.net.cn

如果未设置 symmetricStore,它将默认为 keyStore。如果未设置密钥或信任库,回调处理程序将使用标准的 Java 机制来加载或创建它。有关此机制的工作原理,请参阅 KeyStoreCallbackHandler 的 JavaDoc。spring-doc.cadn.net.cn

例如,如果要使用 KeyStoreCallbackHandler 来验证传入的证书或签名,可以使用信任库(trust store):spring-doc.cadn.net.cn

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

如果你想使用它来解密传入的证书或签署传出的消息,可以使用密钥库:spring-doc.cadn.net.cn

<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以及针对特定加密操作需要设置的属性。spring-doc.cadn.net.cn

7.1.2. 认证

正如本章介绍中所述,认证的任务是确定主体是否是他们所声称的身份。在 WS-Security 中,认证可以采取两种形式:使用用户名和密码Tokens(使用明文密码或密码摘要)或使用 X509 证书。spring-doc.cadn.net.cn

纯文本用户名认证

最简单的用户名认证形式使用明文密码。在这种情况下,SOAP 消息包含一个 UsernameToken 元素,该元素本身包含一个 Username 元素和一个包含明文密码的 Password 元素。明文认证可以与 HTTP 服务器提供的基本认证相比较。spring-doc.cadn.net.cn

请注意,纯文本密码的安全性并不高。因此,如果您使用它们,应该始终在传输层添加额外的安全措施(例如,使用 HTTPS 而不是纯 HTTP)。

为了要求每个传入的消息都包含一个带有明文密码的UsernameToken,安全策略文件应该包含一个RequireUsernameToken元素,并将passwordDigestRequired属性设置为false。您可以在此处找到可能的子元素的参考:这里。以下列表显示了如何包含RequireUsernameToken元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireUsernameToken passwordDigestRequired="false" nonceRequired="false"/>
    ...
</xwss:SecurityConfiguration>

如果用户名Tokens不存在,XwsSecurityInterceptor 会向发送方返回一个 SOAP 错误。如果存在,它会使用 PlainTextPasswordRequest 向已注册的处理器触发一个 PasswordValidationCallback。在 Spring-WS 中,有三个类处理这个特定的回调。spring-doc.cadn.net.cn

使用SimplePasswordValidationCallbackHandler

最简单的密码验证处理器是SimplePasswordValidationCallbackHandler。该处理器通过内存中的Properties对象来验证密码,您可以使用users属性来指定该对象:spring-doc.cadn.net.cn

<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”登录。spring-doc.cadn.net.cn

使用SpringPlainTextPasswordValidationCallbackHandler

The SpringPlainTextPasswordValidationCallbackHandler uses Spring Security to authenticate users. It is beyond the scope of this document to describe Spring Security, but it is a full-fledged security framework. You can read more about it in the Spring Security reference documentation.spring-doc.cadn.net.cn

SpringPlainTextPasswordValidationCallbackHandler 需要一个 AuthenticationManager 才能运行。它使用此管理器针对其创建的 UsernamePasswordAuthenticationToken 进行身份验证。如果身份验证成功,Tokens将存储在 SecurityContextHolder 中。您可以使用 authenticationManager 属性设置身份验证管理器:spring-doc.cadn.net.cn

<beans>
  <bean id="springSecurityHandler"
      class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler">
    <property name="authenticationManager" ref="authenticationManager"/>
  </bean>

  <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager">
      <property name="providers">
          <bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
              <property name="userDetailsService" ref="userDetailsService"/>
          </bean>
      </property>
  </bean>

  <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
  ...
</beans>
使用JaasPlainTextPasswordValidationCallbackHandler

The JaasPlainTextPasswordValidationCallbackHandler 是基于标准的 Java 认证和授权服务。在本文档中全面介绍 JAAS 超出了范围,但可以参考一个 很好的教程spring-doc.cadn.net.cn

JaasPlainTextPasswordValidationCallbackHandler 只需要一个 loginContextName 即可运行。它通过使用此名称创建一个新的 JAAS LoginContext,并通过使用 SOAP 消息中提供的用户名和密码处理标准的 JAAS NameCallbackPasswordCallback。这意味着此回调处理器可以与任何在 login() 阶段触发这些回调的 JAAS LoginModule 集成,这是标准行为。spring-doc.cadn.net.cn

你可以按如下方式连接一个JaasPlainTextPasswordValidationCallbackHandlerspring-doc.cadn.net.cn

<bean id="jaasValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasPlainTextPasswordValidationCallbackHandler">
    <property name="loginContextName" value="MyLoginModule" />
</bean>

在本例中,回调处理程序使用名为 MyLoginModuleLoginContext。此模块应定义在您的 jaas.config 文件中,如先前提到的 教程中所述spring-doc.cadn.net.cn

摘要用户名认证

当使用密码摘要时,SOAP 消息还包含一个 UsernameToken 元素,该元素本身包含一个 Username 元素和一个 Password 元素。区别在于密码不是以明文形式发送的,而是作为摘要发送。接收方将此摘要与他根据用户已知密码计算出的摘要进行比较,如果两者相同,则用户通过身份验证。这种方法类似于 HTTP 服务器提供的摘要认证。spring-doc.cadn.net.cn

要求每条传入消息都包含一个带有密码摘要的UsernameToken元素时,安全策略文件应包含一个RequireUsernameToken元素,并将passwordDigestRequired属性设置为true。此外,nonceRequired属性应设置为true:您可以在此处找到可能的子元素的参考:这里。以下代码清单展示了如何定义RequireUsernameToken元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireUsernameToken passwordDigestRequired="true" nonceRequired="true"/>
    ...
</xwss:SecurityConfiguration>

如果用户名Tokens不存在,XwsSecurityInterceptor会向发送方返回一个SOAP错误。如果存在,它会通过PasswordValidationCallback触发一个带有DigestPasswordRequest的事件给已注册的处理器。在Spring-WS中,有两个类处理这个特定的回调:SimplePasswordValidationCallbackHandlerSpringDigestPasswordValidationCallbackHandlerspring-doc.cadn.net.cn

使用SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler 可以处理纯文本密码以及密码摘要。在 使用 SimplePasswordValidationCallbackHandler 中有相关描述。spring-doc.cadn.net.cn

使用SpringDigestPasswordValidationCallbackHandler

SpringDigestPasswordValidationCallbackHandler 需要一个 Spring Security 的 UserDetailService 才能运行。它使用此服务检索Tokens中指定用户的密码。然后将此详细信息对象中包含的密码摘要与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且会将 UsernamePasswordAuthenticationToken 存储在 SecurityContextHolder 中。您可以使用 userDetailsService 属性来设置该服务。此外,您还可以设置一个 userCache 属性,以缓存加载的用户详细信息。以下示例展示了如何实现这一点:spring-doc.cadn.net.cn

<beans>
    <bean class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>

    <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
    ...
</beans>
证书认证

一种更安全的认证方式是使用 X509 证书。在这种场景下,SOAP 消息包含一个 `BinarySecurityToken`,其中包含一个 Base64 编码的 X509 证书版本。接收方使用该证书进行身份验证。消息中存储的证书还用于对消息进行签名(参见 验证签名)。spring-doc.cadn.net.cn

为了确保所有传入的 SOAP 消息都包含一个 `BinarySecurityToken`,安全策略文件应包含一个 RequireSignature 元素。该元素还可以进一步包含其他元素,这些内容在 验证签名 中有所介绍。您可以在此处找到可能的子元素参考:这里。以下代码清单展示了如何定义一个 RequireSignature 元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    ...
    <xwss:RequireSignature requireTimestamp="false">
    ...
</xwss:SecurityConfiguration>

当收到未携带证书的消息时,XwsSecurityInterceptor 会向发送方返回一个 SOAP 错误。如果存在,则触发 CertificateValidationCallback。Spring-WS 中的三个处理器会处理此回调以用于身份验证目的:spring-doc.cadn.net.cn

在大多数情况下,证书认证之前应该进行证书验证,因为您只想对有效证书进行认证。无效的证书,例如已过期或不在您的受信任证书存储中的证书,应被忽略。spring-doc.cadn.net.cn

在 Spring-WS 的术语中,这意味着 SpringCertificateValidationCallbackHandlerJaasCertificateValidationCallbackHandler 应该位于 KeyStoreCallbackHandler 之前。这可以通过在 XwsSecurityInterceptor 的配置中设置 callbackHandlers 属性的顺序来实现:spring-doc.cadn.net.cn

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

使用此设置,拦截器首先通过密钥库确定消息中的证书是否有效,然后对其进行身份验证。spring-doc.cadn.net.cn

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler 使用标准的 Java 密钥库来验证证书。此证书验证过程包括以下步骤:。spring-doc.cadn.net.cn

  1. 处理程序检查证书是否在私有的 keyStore 中。如果是,则该证书有效。spring-doc.cadn.net.cn

  2. 如果证书不在私有密钥库中,处理器会检查当前日期和时间是否在证书中给出的有效期内。如果不在,则证书无效。如果在,则继续执行最后一步。spring-doc.cadn.net.cn

  3. 为证书创建了证书路径。这基本上意味着处理器会确定该证书是否由 trustStore 中的任何证书颁发机构签发。如果能够成功构建证书路径,则该证书是有效的。否则,证书无效。spring-doc.cadn.net.cn

要将 KeyStoreCallbackHandler 用于证书验证目的,您很可能只需要设置 trustStore 属性:spring-doc.cadn.net.cn

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

使用前面示例中显示的设置,需要验证的证书本身必须位于信任库中,或者信任库必须包含颁发该证书的证书颁发机构。spring-doc.cadn.net.cn

使用SpringCertificateValidationCallbackHandler

SpringCertificateValidationCallbackHandler 需要一个 Spring Security 的 AuthenticationManager 才能运行。它使用这个管理器来针对其创建的 X509AuthenticationToken 进行身份验证。配置的身份验证管理器需要提供一个能够处理此Tokens的提供程序(通常是 X509AuthenticationProvider 的实例)。如果身份验证成功,Tokens将存储在 SecurityContextHolder 中。你可以通过使用 authenticationManager 属性来设置身份验证管理器:spring-doc.cadn.net.cn

<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 参考文档spring-doc.cadn.net.cn

使用JaasCertificateValidationCallbackHandler

JaasCertificateValidationCallbackHandler 需要 loginContextName 才能运行。它通过使用此名称和证书的 X500Principal 创建一个新的 JAAS LoginContext。这意味着此回调处理程序可以与任何处理 X500 主体的 JAAS LoginModule 集成。spring-doc.cadn.net.cn

你可以按如下方式连接一个JaasCertificateValidationCallbackHandlerspring-doc.cadn.net.cn

<bean id="jaasValidationHandler"
    class="org.springframework.ws.soap.security.xwss.callback.jaas.JaasCertificateValidationCallbackHandler">
    <property name="loginContextName">MyLoginModule</property>
</bean>

在这种情况下,回调处理程序使用名为 MyLoginModuleLoginContext。该模块应定义在您的 jaas.config 文件中,并且应该能够针对 X500 主体进行身份验证。spring-doc.cadn.net.cn

7.1.3. 数字签名

消息的数字签名是基于文档和签名者私钥的一段信息。在 WS-Security 中,与签名相关的两个主要任务是验证签名和签署消息。spring-doc.cadn.net.cn

验证签名

基于证书的身份验证一样,签名消息包含一个BinarySecurityToken,其中包含用于签署消息的证书。此外,它还包含一个SignedInfo块,指示消息的哪一部分被签名。spring-doc.cadn.net.cn

为确保所有传入的 SOAP 消息都带有 BinarySecurityToken,安全策略文件应包含一个 RequireSignature 元素。它还可以包含一个 SignatureTarget 元素,该元素指定预期要签名的目标消息部分以及各种其他子元素。您还可以定义要使用的私钥别名、是否使用对称密钥代替私钥,以及许多其他属性。您可以在此处找到可能的子元素参考:这里。以下清单配置了一个 RequireSignature 元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:RequireSignature requireTimestamp="false"/>
</xwss:SecurityConfiguration>

如果签名不存在,XwsSecurityInterceptor 会向发送方返回一个 SOAP 错误。如果存在,它会向注册的处理器触发一个 SignatureVerificationKeyCallback。在 Spring-WS 中,有一个类处理这个特定的回调:KeyStoreCallbackHandlerspring-doc.cadn.net.cn

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用java.security.KeyStore来处理各种加密回调,包括签名验证。对于签名验证,处理器使用trustStore属性:spring-doc.cadn.net.cn

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="trustStore" ref="trustStore"/>
    </bean>

    <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-truststore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>
消息签名

当对消息进行签名时,XwsSecurityInterceptor 会将 BinarySecurityToken 添加到消息中。它还会添加一个 SignedInfo 块,用于指示消息的哪一部分被签名。spring-doc.cadn.net.cn

为了签署所有传出的 SOAP 消息,安全策略文件应包含一个 Sign 元素。它还可以包含一个 SignatureTarget 元素,该元素指定预期要签名的目标消息部分以及各种其他子元素。您还可以定义要使用的私钥别名、是否使用对称密钥代替私钥,以及许多其他属性。您可以在此处找到可能的子元素参考:这里。以下示例包含一个 Sign 元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
	<xwss:Sign includeTimestamp="false" />
</xwss:SecurityConfiguration>

XwsSecurityInterceptor 触发一个 SignatureKeyCallback 到已注册的处理器。在 Spring-WS 中,KeyStoreCallbackHandler 类处理这个特定的回调。spring-doc.cadn.net.cn

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用java.security.KeyStore来处理各种加密回调,包括消息签名。对于添加签名,处理器使用keyStore属性。此外,您必须设置privateKeyPassword属性以解锁用于签名的私钥。以下示例使用了KeyStoreCallbackHandlerspring-doc.cadn.net.cn

<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.1.4. 解密与加密

加密时,消息会被转换为只有使用适当的密钥才能读取的形式。消息可以被解密以揭示原始的、可读的消息。spring-doc.cadn.net.cn

解密

为了解密传入的 SOAP 消息,安全策略文件应包含一个 RequireEncryption 元素。该元素还可以进一步携带一个 EncryptionTarget 元素,用于指示消息的哪一部分应该被加密,以及一个 SymmetricKey 元素,用于指示使用共享密钥而非常规私钥来解密消息。您可以在此处阅读其他元素的描述:这里。以下示例使用了一个 RequireEncryption 元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:RequireEncryption />
</xwss:SecurityConfiguration>

如果传入的消息未加密,XwsSecurityInterceptor会向发送方返回一个SOAP错误。如果存在,它会向注册的处理器触发一个DecryptionKeyCallback。在Spring-WS中,KeyStoreCallbackHandler类处理此特定回调。spring-doc.cadn.net.cn

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用java.security.KeyStore来处理各种加密回调,包括解密。对于解密,处理器使用keyStore属性。此外,您必须设置privateKeyPassword属性以解锁用于解密的私钥。对于基于对称密钥的解密,它使用symmetricStore。以下示例使用KeyStoreCallbackHandlerspring-doc.cadn.net.cn

<beans>
    <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
        <property name="keyStore" ref="keyStore"/>
        <property name="privateKeyPassword" value="changeit"/>
    </bean>

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
        <property name="location" value="classpath:keystore.jks"/>
        <property name="password" value="changeit"/>
    </bean>
</beans>
加密

为了加密传出的 SOAP 消息,安全策略文件应包含一个 Encrypt 元素。该元素还可以携带一个 EncryptionTarget 元素,用于指示消息的哪一部分应被加密,以及一个 SymmetricKey 元素,用于指示使用共享密钥而不是常规公钥来加密消息。您可以在此处阅读其他元素的描述:这里。以下示例使用了一个 Encrypt 元素:spring-doc.cadn.net.cn

<xwss:SecurityConfiguration xmlns:xwss="http://java.sun.com/xml/ns/xwss/config">
    <xwss:Encrypt />
</xwss:SecurityConfiguration>

XwsSecurityInterceptor 触发一个 EncryptionKeyCallback 到已注册的处理器以获取加密信息。在 Spring-WS 中,KeyStoreCallbackHandler 类处理这个特定的回调。spring-doc.cadn.net.cn

使用KeyStoreCallbackHandler

KeyStoreCallbackHandler中所述,KeyStoreCallbackHandler使用java.security.KeyStore来处理各种加密回调,包括加密。对于基于公钥的加密,处理器使用trustStore属性。对于基于对称密钥的加密,则使用symmetricStore。以下示例使用KeyStoreCallbackHandlerspring-doc.cadn.net.cn

<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.1.5. 安全异常处理

当一个安全操作或验证操作失败时,XwsSecurityInterceptor 分别抛出 WsSecuritySecurementExceptionWsSecurityValidationException。这些异常绕过了标准异常处理机制,但会被拦截器自身处理。spring-doc.cadn.net.cn

WsSecuritySecurementException 个异常由 handleSecurementException 方法的 XwsSecurityInterceptor 处理。默认情况下,此方法会记录错误并停止消息的进一步处理。spring-doc.cadn.net.cn

同样,WsSecurityValidationException 异常由 handleValidationException 方法的 XwsSecurityInterceptor 处理。默认情况下,此方法创建一个 SOAP 1.1 客户端或 SOAP 1.2 发送方错误,并将其作为响应发送回去。spring-doc.cadn.net.cn

handleSecurementExceptionhandleValidationException 都是受保护的方法,您可以重写它们以改变其默认行为。

7.2. 使用 Wss4jSecurityInterceptor

Wss4jSecurityInterceptor 是一个基于 Apache 的 WSS4JEndpointInterceptor(参见 拦截请求 —— EndpointInterceptor 接口)。spring-doc.cadn.net.cn

WSS4J 实现了以下标准:spring-doc.cadn.net.cn

此拦截器支持由AxiomSoapMessageFactorySaajSoapMessageFactory创建的消息。spring-doc.cadn.net.cn

7.2.1. 配置Wss4jSecurityInterceptor

WSS4J 不使用外部配置文件。拦截器完全通过属性进行配置。此拦截器调用的验证和安全操作分别通过 validationActionssecurementActions 属性指定。操作以空格分隔的字符串形式传递。以下列表显示了一个示例配置:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="UsernameToken Encrypt"/>
    ...
    <property name="securementActions" value="Encrypt"/>
    ...
</bean>

下表显示了可用的验证操作:spring-doc.cadn.net.cn

验证操作 描述

UsernameTokenspring-doc.cadn.net.cn

验证用户名Tokensspring-doc.cadn.net.cn

Timestampspring-doc.cadn.net.cn

验证时间戳spring-doc.cadn.net.cn

Encryptspring-doc.cadn.net.cn

解密消息spring-doc.cadn.net.cn

Signaturespring-doc.cadn.net.cn

验证签名spring-doc.cadn.net.cn

NoSecurityspring-doc.cadn.net.cn

未执行任何操作spring-doc.cadn.net.cn

下表显示了可用的安全操作:spring-doc.cadn.net.cn

安全措施 描述

UsernameTokenspring-doc.cadn.net.cn

添加用户名Tokensspring-doc.cadn.net.cn

UsernameTokenSignaturespring-doc.cadn.net.cn

添加用户名Tokens和签名用户名Tokens密钥spring-doc.cadn.net.cn

Timestampspring-doc.cadn.net.cn

添加时间戳spring-doc.cadn.net.cn

Encryptspring-doc.cadn.net.cn

加密响应spring-doc.cadn.net.cn

Signaturespring-doc.cadn.net.cn

签名响应spring-doc.cadn.net.cn

NoSecurityspring-doc.cadn.net.cn

未执行任何操作spring-doc.cadn.net.cn

拦截器会强制执行操作的顺序。如果其安全操作的执行顺序与`validationActions`中指定的顺序不同,拦截器将拒绝传入的SOAP消息。spring-doc.cadn.net.cn

7.2.2. 处理数字证书

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

Crypto 个实例可以从 WSS4J 的 CryptoFactory 获取,或者更方便地使用 Spring-WS 的 `CryptoFactoryBean`。spring-doc.cadn.net.cn

CryptoFactoryBean

Spring-WS 提供了一个便捷的工厂 bean,CryptoFactoryBean,它通过强类型属性(推荐)或通过Properties对象来构造和配置Crypto实例。spring-doc.cadn.net.cn

默认情况下,CryptoFactoryBean 返回 org.apache.ws.security.components.crypto.Merlin 的实例。您可以通过设置 cryptoProvider 属性(或其等效的 org.apache.ws.security.crypto.provider 字符串属性)来更改此行为。spring-doc.cadn.net.cn

以下示例配置使用 CryptoFactoryBeanspring-doc.cadn.net.cn

<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.2.3. 认证

本节讨论如何使用 Wss4jSecurityInterceptor 进行身份验证。spring-doc.cadn.net.cn

验证用户名Tokens

Spring-WS 提供了一组回调处理器以与 Spring Security 集成。此外,还提供了一个简单的回调处理器 SimplePasswordValidationCallbackHandler,用于通过内存中的 Properties 对象配置用户和密码。spring-doc.cadn.net.cn

回调处理程序是通过 Wss4jSecurityInterceptor 属性的 validationCallbackHandler 配置的。spring-doc.cadn.net.cn

使用SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler 验证纯文本和摘要用户名Tokens,与内存中的 Properties 对象进行比对。您可以按以下方式对其进行配置:spring-doc.cadn.net.cn

<bean id="callbackHandler"
    class="org.springframework.ws.soap.security.wss4j.callback.SimplePasswordValidationCallbackHandler">
    <property name="users">
        <props>
            <prop key="Bert">Ernie</prop>
        </props>
    </property>
</bean>
使用SpringSecurityPasswordValidationCallbackHandler

SpringSecurityPasswordValidationCallbackHandler 通过使用 Spring Security 的 UserDetailService 来验证纯文本和摘要密码。它使用此服务检索Tokens中指定用户的密码(或密码的摘要)。然后将此详细信息对象中包含的密码(或密码的摘要)与消息中的摘要进行比较。如果两者相等,则用户已成功通过身份验证,并且会将 UsernamePasswordAuthenticationToken 存储在 `SecurityContextHolder` 中。您可以通过使用 userDetailsService 来设置该服务。此外,您可以设置一个 userCache 属性,以缓存加载的用户详细信息,如下所示:spring-doc.cadn.net.cn

<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>
添加用户名Tokens

将用户名Tokens添加到传出消息就像在 Wss4jSecurityInterceptorsecurementActions 属性中添加 UsernameToken 并指定 securementUsername 和 `securementPassword` 一样简单。spring-doc.cadn.net.cn

可以通过设置securementPasswordType属性来设置密码类型。可能的值是PasswordText表示明文密码,或PasswordDigest表示摘要密码,默认值为摘要密码。spring-doc.cadn.net.cn

以下示例生成带有摘要密码的用户名Tokens:spring-doc.cadn.net.cn

<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属性来指示拦截器添加NonceCreated元素。该值必须是一个包含所需元素名称的列表,名称之间以空格分隔(区分大小写)。spring-doc.cadn.net.cn

以下示例生成一个带有明文密码的用户名Tokens,一个 Nonce 元素和一个 Created 元素:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
    <property name="securementPasswordType" value="PasswordText"/>
    <property name="securementUsernameTokenElements" value="Nonce Created"/>
</bean>
证书认证

由于证书认证类似于数字签名,WSS4J 将其作为签名验证和安全保障的一部分来处理。具体来说,必须将 securementSignatureKeyIdentifier 属性设置为 DirectReference,以指示 WSS4J 生成一个包含 X509 证书的 BinarySecurityToken 元素,并将其包含在传出消息中。证书的名称和密码分别通过 securementUsernamesecurementPassword 属性传递,如下例所示:spring-doc.cadn.net.cn

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

对于证书验证,适用常规签名验证:spring-doc.cadn.net.cn

<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 方法来更改此行为。spring-doc.cadn.net.cn

欲了解更多信息,请参阅数字签名spring-doc.cadn.net.cn

7.2.4. 安全时间戳

本节描述了Wss4jSecurityInterceptor中可用的各种时间戳选项。spring-doc.cadn.net.cn

验证时间戳

为了验证时间戳,请将 Timestamp 添加到 validationActions 属性中。您可以通过将 timestampStrict 设置为 true 来覆盖由 SOAP 消息发起者指定的时间戳语义,并通过设置 timeToLive 属性来指定服务器端的生存时间(以秒为单位,默认值:300)。无论 timeToLive 的值是什么,拦截器始终会拒绝已过期的时间戳。spring-doc.cadn.net.cn

在以下示例中,拦截器将时间戳的有效窗口限制为10秒,拒绝该窗口之外的任何有效时间戳Tokens:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Timestamp"/>
    <property name="timestampStrict" value="true"/>
    <property name="timeToLive" value="10"/>
</bean>
添加时间戳

Timestamp 添加到 securementActions 属性会在传出消息中生成时间戳头。 timestampPrecisionInMilliseconds 属性指定生成的时间戳的精度是否为毫秒级。 默认值是 true。 以下列表添加了一个时间戳:spring-doc.cadn.net.cn

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

7.2.5. 数字签名

本节描述了Wss4jSecurityInterceptor中可用的各种签名选项。spring-doc.cadn.net.cn

验证签名

为了指示Wss4jSecurityInterceptorvalidationActions必须包含Signature操作。此外,validationSignatureCrypto属性必须指向包含启动者公共证书的密钥库:spring-doc.cadn.net.cn

<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>
消息签名

通过将Signature操作添加到securementActions来启用对传出消息的签名。私钥的别名和密码分别由securementUsernamesecurementPassword属性指定。securementSignatureCrypto必须指向包含私钥的密钥库:spring-doc.cadn.net.cn

<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 属性来定义签名算法。spring-doc.cadn.net.cn

你可以通过设置securementSignatureKeyIdentifier属性来自定义要使用的键标识符类型。只有IssuerSerialDirectReference对签名有效。spring-doc.cadn.net.cn

securementSignatureParts 属性控制消息的哪一部分被签名。此属性的值是一个由分号分隔的元素名称列表,用于标识要签名的元素。签名部分的一般形式为 {}{namespace}Element。请注意,第一组空括号仅用于加密部分。默认行为是签署 SOAP 正文。spring-doc.cadn.net.cn

以下示例显示了如何在 Spring Web Services 回声示例中签署 echoResponse 元素:spring-doc.cadn.net.cn

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

要指定一个没有命名空间的元素,请使用字符串 Null(区分大小写)作为命名空间名称。spring-doc.cadn.net.cn

如果请求中的其他元素都没有本地名称为 Body,则 SOAP 命名空间标识符可以为空 ({})。spring-doc.cadn.net.cn

签名确认

通过将enableSignatureConfirmation设置为true来启用签名确认。请注意,签名确认操作跨越请求和响应。这意味着即使没有相应的安全操作,也必须将secureResponsevalidateRequest设置为true(这是默认值)。以下示例将enableSignatureConfirmation属性设置为truespring-doc.cadn.net.cn

<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.2.6. 解密与加密

本节描述了Wss4jSecurityInterceptor中可用的各种解密和加密选项。spring-doc.cadn.net.cn

解密

解密传入的 SOAP 消息需要将 Encrypt 操作添加到 validationActions 属性中。其余的配置取决于消息中出现的密钥信息。(这是因为 WSS4J 只需要一个用于加密密钥的 Crypto,而嵌入式密钥名称验证则委托给回调处理器。)spring-doc.cadn.net.cn

要解密带有嵌入式加密对称密钥的消息(xenc:EncryptedKey 元素),validationDecryptionCrypto 需要指向包含解密私钥的密钥库。此外,validationCallbackHandler 必须注入一个指定密钥密码的 org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandlerspring-doc.cadn.net.cn

<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 元素指定:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="classpath:keystore.jks"/>
                    <property name="type" value="JCEKS"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
            <property name="symmetricKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>
加密

Encrypt添加到securementActions可以启用传出消息的加密。您可以通过设置securementEncryptionUser属性来设置用于加密的证书别名。证书所在的密钥库通过securementEncryptionCrypto属性进行访问。由于加密依赖于公钥证书,因此无需传递密码。以下示例使用了securementEncryptionCrypto属性:spring-doc.cadn.net.cn

<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属性定义。可能的值包括IssuerSerialX509KeyIdentifierDirectReferenceThumbprintSKIKeyIdentifierEmbeddedKeyNamespring-doc.cadn.net.cn

如果选择 EmbeddedKeyName 类型,则需要指定用于加密的密钥。密钥的别名设置在 securementEncryptionUser 属性中,类似于其他密钥标识符类型。然而,WSS4J 需要一个回调处理器来获取密钥。因此,您必须为 securementCallbackHandler 提供一个指向适当密钥库的 KeyStoreCallbackHandler。默认情况下,生成的 WS-Security 头部中的 ds:KeyName 元素会取 securementEncryptionUser 属性的值。若要指定不同的名称,您可以将 securementEncryptionEmbeddedKeyName 设置为所需的值。在下一个示例中,传出消息使用别名为 secretKey 的密钥进行加密,而 myKey 出现在 ds:KeyName 元素中:spring-doc.cadn.net.cn

<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-mgf1pspring-doc.cadn.net.cn

你可以通过设置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-cbcspring-doc.cadn.net.cn

最后,securementEncryptionParts 属性定义了消息的哪些部分被加密。此属性的值是一个由分号分隔的元素名称列表,用于标识要加密的元素。每个元素名称之前可能带有加密模式说明符和命名空间标识,分别位于一对大括号内。加密模式说明符可以是 {Content}{Element}。有关元素加密和内容加密之间的区别,请参阅 W3C XML 加密规范。以下示例标识了来自 echo 示例的 echoResponsespring-doc.cadn.net.cn

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

请注意,元素名称、命名空间标识符和加密修饰符是区分大小写的。您可以省略加密修饰符和命名空间标识符。如果省略,加密模式将默认为Content,命名空间将设置为SOAP命名空间。spring-doc.cadn.net.cn

要指定一个没有命名空间的元素,请使用值 Null(区分大小写)作为命名空间名称。如果没有指定列表,处理器默认会以 Content 模式加密 SOAP Body。spring-doc.cadn.net.cn

7.2.7. 安全异常处理

Wss4jSecurityInterceptor 的异常处理与 XwsSecurityInterceptor 的异常处理相同。有关更多信息,请参阅 安全异常处理spring-doc.cadn.net.cn