当前位置 : 主页 > 编程语言 > java >

构成 Spring Web 服务的各种组件(二)

来源:互联网 收集:自由互联 发布时间:2023-02-04
6. 在客户端上使用 Spring Web 服务 Spring-WS提供了一个客户端Web服务API,允许对Web服务进行一致的XML驱动访问。它还迎合了编组程序和取消编组程序的使用,以便服务层代码可以专门处理

构成 Spring Web 服务的各种组件(二)_Web

6. 在客户端上使用 Spring Web 服务

Spring-WS提供了一个客户端Web服务API,允许对Web服务进行一致的XML驱动访问。它还迎合了编组程序和取消编组程序的使用,以便服务层代码可以专门处理 Java 对象。

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

  • 发送和接收 XML 消息
  • 在发送之前将对象封送到 XML
  • 允许多种运输选择

6.1. 使用客户端 API

本节介绍如何使用客户端 API。有关如何使用服务器端 API,请参阅 使用 Spring-WS 创建 Web 服务。

6.1.1.​​WebServiceTemplate​​

这是 Spring-WS 中客户端 Web 服务访问的核心类。它包含用于发送对象和接收响应消息作为 or 的方法。此外,它可以在通过传输发送对象之前将对象封送到 XML,并再次将任何响应 XML 取消封送到对象中。​​WebServiceTemplate​​​​Source​​​​Source​​​​Result​​

URI 和传输

该类使用 URI 作为消息目标。可以在模板本身上设置属性,也可以在模板上调用方法时显式提供 URI。URI 解析为 ,负责跨传输层发送 XML 消息。可以使用类的 or 属性设置一个或多个邮件发件人。​​WebServiceTemplate​​​​defaultUri​​​​WebServiceMessageSender​​​​messageSender​​​​messageSenders​​​​WebServiceTemplate​​

HTTP 传输

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

若要使用 HTTP 传输,请将 设置为 like 或为其中一个方法提供参数。​​defaultUri​​​​http://example.com/services​​​​uri​​

以下示例演示如何使用 HTTP 传输的默认配置:

<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 身份验证进行身份验证:

<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发送消息,Spring Web Services提供了.这个类使用Spring框架的功能将JMS转换为JMS,在或上发送它,并接收响应(如果有的话)。​​JmsMessageSender​​​​WebServiceMessage​​​​Message​​​​Queue​​​​Topic​​

要使用 ,您需要将 or 参数设置为 JMS URI,该 URI 至少由前缀和目标名称组成。JMS URI 的一些示例包括:、 和 。有关此 URI 语法的更多信息,请参阅 Javadoc for JmsMessageSender。​​JmsMessageSender​​​​defaultUri​​​​uri​​​​jms:​​​​jms:SomeQueue​​​​jms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENT​​​​jms:RequestQueue?replyToName=ResponseName​​

默认情况下,发送 JMS ,但您可以使用 JMS URI 上的参数覆盖它以使用,例如 。请注意,这是首选类型,因为 不支持附件和字符编码。​​JmsMessageSender​​​​BytesMessage​​​​TextMessages​​​​messageType​​​​jms:Queue?messageType=TEXT_MESSAGE​​​​BytesMessages​​​​TextMessages​​

以下示例显示如何将 JMS 传输与 ActiveMQ 连接工厂结合使用:

<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 检索它们。客户端电子邮件功能包含在类中。此类从请求创建电子邮件,并通过 SMTP 发送。然后,它等待响应消息到达传入的 POP3 或 IMAP 服务器。​​MailMessageSender​​​​WebServiceMessage​​

要使用 ,请将 or 参数设置为 URI,例如 or 。确保邮件发件人正确配置了 ,前者指示用于发送请求的服务器(通常是 SMTP 服务器),后者指示要轮询响应的服务器(通常为 POP3 或 IMAP 服务器)。​​MailMessageSender​​​​defaultUri​​​​uri​​​​mailto​​​​mailto:john@example.com​​​​mailto:server@localhost?subject=SOAP%20Test​​​​transportUri​​​​storeUri​​

以下示例演示如何使用电子邮件传输:

<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 <client@example.com>"/> <property name="transportUri" value="smtp://client:s04p@smtp.example.com"/> <property name="storeUri" value="imap://client:s04p@imap.example.com/INBOX"/> </bean> </property> <property name="defaultUri" value="mailto:server@example.com?subject=SOAP%20Test"/> </bean></beans>
XMPP 运输

Spring Web Services 2.0引入了XMPP(Jabber)传输,您可以使用它通过XMPP发送和接收Web服务消息。客户端 XMPP 功能包含在类中。此类从请求创建 XMPP 消息,并通过 XMPP 发送该消息。然后,它会侦听响应消息到达。​​XmppMessageSender​​​​WebServiceMessage​​

要使用 ,请将 or 参数设置为 URI,例如 。发送方还需要 才能工作,可以使用 .​​XmppMessageSender​​​​defaultUri​​​​uri​​​​xmpp​​​​xmpp:johndoe@jabber.org​​​​XMPPConnection​​​​org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean​​

以下示例演示如何使用 XMPP 传输:

<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:user@jabber.org"/> </bean></beans>
消息工厂

除了消息发送者之外,还需要 Web 服务消息工厂。SOAP 有两个消息工厂:和 。如果未指定消息工厂(通过设置属性),则默认情况下 Spring-WS 使用 。​​WebServiceTemplate​​​​SaajSoapMessageFactory​​​​AxiomSoapMessageFactory​​​​messageFactory​​​​SaajSoapMessageFactory​​

6.1.2. 发送和接收​​WebServiceMessage​​

包含许多发送和接收 Web 服务消息的便捷方法。有接受并返回 a 的方法,也有返回 .此外,还有一些方法可以将对象封送和取消封送到 XML。下面的示例将简单的 XML 消息发送到 Web 服务:​​WebServiceTemplate​​​​Source​​​​Result​​

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>

前面的示例使用 将“Hello, World”消息发送到位于(在方法的情况下)的 Web 服务,并将结果写入控制台。注入了缺省 URI,之所以使用该 URI,是因为 Java 代码中没有显式提供 URI。​​WebServiceTemplate​​​​http://localhost:8080/WebService​​​​simpleSendAndReceive()​​​​WebServiceTemplate​​

请注意,该类一旦配置,就是线程安全的(假设它的所有依赖项也是线程安全的,Spring-WS 附带的所有依赖项都是这种情况),因此多个对象可以使用同一个共享实例。它公开了一个零参数构造函数和 Bean 属性,您可以使用这些构造函数和 Bean 属性(通过使用 Spring 容器或纯 Java 代码)。或者,考虑从 Spring-WS 的便利基类派生,该基类公开了方便的 Bean 属性以实现轻松配置。(您不必扩展此基类。它仅作为便利类提供。​​WebServiceTemplate​​​​WebServiceTemplate​​​​WebServiceTemplate​​​​messageFactory​​​​messageSender​​​​WebServiceGatewaySupport​​

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

为了便于发送纯 Java 对象,有许多方法将 a 作为消息数据内容的参数。类中的方法将请求对象到 XML 的转换委托给 ,并将响应 XML 到对象的转换委托给 。(有关编组和取消编组器的更多信息,请参阅 Spring 框架参考文档。通过使用封送器,应用程序代码可以专注于正在发送或接收的业务对象,而不关心它如何表示为 XML 的详细信息。若要使用编组功能,必须使用类的 and 属性设置编组器和取消编组器。​​WebServiceTemplate​​​​send(..)​​​​Object​​​​marshalSendAndReceive(..)​​​​WebServiceTemplate​​​​Marshaller​​​​Unmarshaller​​​​marshaller​​​​unmarshaller​​​​WebServiceTemplate​​

6.1.4. 使用​​WebServiceMessageCallback​​

为了适应在消息上设置 SOAP 标头和其他设置,该接口允许您在创建消息之后但在发送消息之前访问消息。下面的示例演示如何在通过封送对象创建的消息上设置 SOAP 操作标头:​​WebServiceMessageCallback​​

public void marshalWithSoapActionHeader(MyObject o) { webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() { public void doWithMessage(WebServiceMessage message) { ((SoapMessage)message).setSoapAction("http://tempuri.org/Action"); } });}

请注意,您还可以使用 来设置 SOAP 操作标头。​​org.springframework.ws.soap.client.core.SoapActionCallback​​

WS-寻址

除了服务器端WS-Addressing支持之外,Spring Web Services在客户端也支持此规范。

要在客户端上设置 WS 寻址标头,可以使用 。此回调将所需的操作标头作为参数。它还具有用于指定 WS 寻址版本和标头的构造函数。如果未指定,则标头默认为正在建立的连接的 URL。​​org.springframework.ws.soap.addressing.client.ActionCallback​​​​To​​​​To​​

下面的示例将标头设置为 :​​Action​​​​http://samples/RequestOrder​​

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

6.1.5. 使用​​WebServiceMessageExtractor​​

该接口是一个低级回调接口,您可以完全控制从收到的 .在与服务资源的基础连接仍处于打开状态时,在提供的 上调用该方法。以下示例显示了 in 操作:​​WebServiceMessageExtractor​​​​Object​​​​WebServiceMessage​​​​WebServiceTemplate​​​​extractData(..)​​​​WebServiceMessageExtractor​​​​WebServiceMessageExtractor​​

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 服务客户端(即使用 访问 Web 服务的类)时,有两种可能的方法:​​WebServiceTemplate​​

  • 编写单元测试,模拟类、接口或完整的客户端类。WebServiceTemplateWebServiceOperations这种方法的优点是很容易实现。缺点是,您并没有真正测试通过网络发送的 XML 消息的确切内容,尤其是在模拟整个客户端类时。
  • 编写集成测试,测试消息的内容。

第一种方法可以通过模拟框架轻松实现,例如EasyMock,JMock等。下一节重点介绍如何使用 Spring Web Services 2.0 中引入的测试功能编写集成测试。

6.2.1. 编写客户端集成测试

Spring Web Services 2.0引入了对创建Web服务客户端集成测试的支持。在此上下文中,客户端是使用 访问 Web 服务的类。​​WebServiceTemplate​​

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

的典型用法是:。​​MockWebServiceServer​​

  • 通过调用 、 或 创建实例。MockWebServiceServerMockWebServiceServer.createServer(WebServiceTemplate)MockWebServiceServer.createServer(WebServiceGatewaySupport)MockWebServiceServer.createServer(ApplicationContext)
  • 通过调用 来设置请求期望,可能使用 中提供的默认实现(可以静态导入)。可以通过链接呼叫来设置多个期望。expect(RequestMatcher)RequestMatcherRequestMatchersandExpect(RequestMatcher)
  • 通过调用 来创建适当的响应消息,可能使用 中提供的默认实现(可以静态导入)。andRespond(ResponseCreator)ResponseCreatorResponseCreators
  • 像往常一样使用,直接或通过客户端代码。WebServiceTemplate
  • 致电以确保满足所有期望。MockWebServiceServer.verify()
  • 请注意,(和相关类)提供了“流畅”的 API,因此您通常可以使用 IDE 中的代码完成功能来指导您完成设置模拟服务器的过程。​​MockWebServiceServer​​

    另请注意,在单元测试中,您可以依赖 Spring Web Services 中提供的标准日志记录功能。有时,检查请求或响应消息以找出特定测试失败的原因可能很有用。有关详细信息,请参阅消息日志记录和跟踪。

    例如,请考虑以下 Web 服务客户端类:

    public class CustomerClient extends WebServiceGatewaySupport { public int getCustomerCount() { CustomerCountRequest request = new CustomerCountRequest(); request.setCustomerName("John Doe"); CustomerCountResponse response = (CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request); return response.getCustomerCount(); }}

    扩展,这为它提供了一个属性。​​CustomerClient​​​​WebServiceGatewaySupport​​​​webServiceTemplate​​

    ​​CustomerCountRequest​​​是由编组器支持的对象。例如,它可以有一个由 JAXB2 支持的注释。​​@XmlRootElement​​

    使用 提供的 by 将请求对象封送到 SOAP 消息中,并将其发送到 Web 服务。响应对象被解组为 .​​CustomerClient​​​​WebServiceTemplate​​​​WebServiceGatewaySupport​​​​CustomerCountResponse​​

    以下示例显示了 的典型测试:​​CustomerClient​​

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("integration-test.xml") public class CustomerClientIntegrationTest { @Autowired private CustomerClient client; private MockWebServiceServer mockServer; @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)); int result = client.getCustomerCount(); assertEquals(10, result); mockServer.verify(); }}

    导入和静态导入和 .​​CustomerClientIntegrationTest​​​​MockWebServiceServer​​​​RequestMatchers​​​​ResponseCreators​​

    此测试使用 Spring 框架中提供的标准测试工具。这不是必需的,但通常是设置测试的最简单方法。

    使用 配置并连接到此测试中。​​CustomerClient​​​​integration-test.xml​​​​@Autowired​​

    在方法中,我们使用工厂方法创建一个。​​@Before​​​​MockWebServiceServer​​​​createServer​​

    我们通过调用静态导入的 a 来定义期望(请参阅​​使用 RequestMatcher 和 RequestMatchers​​​)。​​expect()​​​​payload()​​​​RequestMatcher​​​​RequestMatchers​​

    我们还通过使用静态导入提供的调用来设置响应(请参阅​​使用 ResponseCreator 和 ResponseCreators​​​)。​​andRespond()​​​​withPayload()​​​​ResponseCreator​​​​ResponseCreators​​

    测试的这一部分可能看起来有点混乱,但 IDE 的代码完成功能非常有用。键入 后,IDE 可以为您提供可能的请求匹配策略列表,前提是静态导入 。这同样适用于 ,前提是您静态导入 。​​expect(​​​​RequestMatchers​​​​andRespond(​​​​ResponseCreators​​

    我们调用 ,因此使用 .到目前为止,模板已设置为“测试模式”,因此此方法调用不会建立真正的 (HTTP) 连接。我们还根据方法调用的结果做出一些 JUnit 断言。​​getCustomerCount()​​​​CustomerClient​​​​WebServiceTemplate​​

    我们调用 ,验证是否实际收到了预期的消息。​​verify()​​​​MockWebServiceServer​​

    6.2.2. 使用 和​​RequestMatcher​​​​RequestMatchers​​

    为了验证请求消息是否满足某些期望,请使用策略接口。此接口定义的协定如下:​​MockWebServiceServer​​​​RequestMatcher​​

    public interface RequestMatcher { void match(URI uri, WebServiceMessage request) throws IOException, AssertionError;}

    您可以编写自己的此接口实现,在消息不符合您的期望时引发异常,但您当然不必这样做。该类提供了标准实现,供您在测试中使用。通常静态导入此类。​​AssertionError​​​​RequestMatchers​​​​RequestMatcher​​

    该类提供以下请求匹配器:​​RequestMatchers​​

    ​​RequestMatchers​​方法

    描述

    ​​anything()​​

    期望任何类型的请求。

    ​​payload()​​

    需要给定的请求有效负载。

    ​​validPayload()​​

    期望请求有效负载根据给定的 XSD 架构进行验证。

    ​​xpath()​​

    期望给定的 XPath 表达式存在、不存在或计算为给定值。

    ​​soapHeader()​​

    期望请求消息中存在给定的 SOAP 标头。

    ​​connectionTo()​​

    需要与给定 URL 的连接。

    您可以通过链接调用来设置多个请求期望:​​andExpect()​​

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

    有关 提供的请求匹配器的更多信息,请参阅 Javadoc。​​RequestMatchers​​

    6.2.3. 使用 和​​ResponseCreator​​​​ResponseCreators​​

    当请求消息经过验证并满足定义的期望时,将创建响应消息供 使用。服务器使用策略接口来实现此目的:​​MockWebServiceServer​​​​WebServiceTemplate​​​​ResponseCreator​​

    public interface ResponseCreator { WebServiceMessage createResponse(URI uri, WebServiceMessage request, WebServiceMessageFactory messageFactory) throws IOException;}

    同样,您可以编写此接口自己的实现,使用消息工厂创建响应消息,但您当然不必这样做,因为该类提供了标准实现供您在测试中使用。通常静态导入此类。​​ResponseCreators​​​​ResponseCreator​​

    该类提供以下响应:​​ResponseCreators​​

    ​​ResponseCreators​​方法

    描述

    ​​withPayload()​​

    创建具有给定有效负载的响应消息。

    ​​withError()​​

    在响应连接中创建错误。此方法使您有机会测试错误处理。

    ​​withException()​​

    从响应连接读取时引发异常。此方法使您有机会测试异常处理。

    ​​withMustUnderstandFault()​​​或​​withClientOrSenderFault()​​​​withServerOrReceiverFault()​​​​withVersionMismatchFault()​​

    创建具有给定 SOAP 错误的响应消息。此方法使您有机会测试故障处理。

    有关 提供的请求匹配器的更多信息,请参阅 Javadoc。​​RequestMatchers​​

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

    本章说明如何将 WS-Security 方面添加到 Web 服务中。我们专注于WS-Security的三个不同领域:

    • 身份验证:这是确定委托人是否是他们声称的身份的过程。在此上下文中,“主体”通常是指可以在应用程序中执行操作的用户、设备或其他系统。
    • 数字签名:邮件的数字签名是基于文档和签名者私钥的一条信息。它是通过使用哈希函数和私有签名函数(使用签名者的私钥加密)创建的。
    • 加密和解密:加密是将数据转换为没有适当密钥就无法读取的形式的过程。它主要用于对任何不打算针对的人隐藏信息。解密与加密相反。这是将加密数据转换回可读形式的过程。

    这三个区域是通过使用 or 来实现的,我们分别在 XwsSecurityInterceptor 和 Using Wss4jSecurityInterceptor 中描述了​​XwsSecurityInterceptor​​​​Wss4jSecurityInterceptor​​

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

    7.1.​​XwsSecurityInterceptor​​

    这是一个基于SUN的XML和Web服务安全包(XWSS)的(参见拦截请求 — 端点拦截器接口)。此 WS-Security 实现是 Java Web Services Developer Pack (Java WSDP) 的一部分。​​XwsSecurityInterceptor​​​​EndpointInterceptor​​

    与任何其他端点拦截器一样,它是在端点映射中定义的(请参阅端点映射)。这意味着您可以有选择地添加 WS-Security 支持。某些终结点映射需要它,而其他终结点映射则不需要。

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

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

    此外,安全拦截器需要一个或多个实例才能运行。这些处理程序用于检索证书、私钥、验证用户凭据等。Spring-WS为最常见的安全问题提供了处理程序,例如,针对Spring Security身份验证管理器进行身份验证,并根据X509证书对传出消息进行签名。以下部分指示用于哪个安全问题的回调处理程序。可以使用 or 属性设置回调处理程序。​​CallbackHandler​​​​callbackHandler​​​​callbackHandlers​​

    以下示例演示如何连接 :​​XwsSecurityInterceptor​​

    <beans> <bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor"> <property name="policyConfiguration" value="classpath:securityPolicy.xml"/> <property name="callbackHandlers"> <list> <ref bean="certificateHandler"/> <ref bean="authenticationHandler"/> </list> </property> </bean> ...</beans>

    此侦听器是使用类路径上的文件配置的。它使用文件后面定义的两个回调处理程序。​​securityPolicy.xml​​

    7.1.1. 密钥库

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

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

    • 私钥:这些密钥用于自我身份验证。私钥附带相应公钥的证书链。在 WS-Security 字段中,这说明了消息签名和消息解密。
    • 对称密钥:对称(或秘密)密钥也用于消息加密和解密 - 区别在于双方(发件人和收件人)共享相同的密钥。
    • 受信任的证书:这些 X509 证书称为“受信任的证书”,因为密钥库所有者信任证书中的公钥确实属于证书的所有者。在 WS-Security 中,这些证书用于证书验证、签名验证和加密。
    用​​keytool​​

    该程序是密钥和证书管理实用程序,随 Java 虚拟机一起提供。您可以使用此工具创建新的密钥库、向其添加新的专用密钥和证书等。提供命令的完整参考超出了本文档的范围,但您可以在此处或使用命令行上的命令找到参考。​​keytool​​​​keytool​​​​keytool -help​​

    用​​KeyStoreFactoryBean​​

    要使用 Spring 配置轻松装入密钥库,可以使用 .它具有资源位置属性,您可以将其设置为指向要装入的密钥库的路径。可以提供密码来检查密钥库数据的完整性。如果未提供密码,则不执行完整性检查。以下清单配置:​​KeyStoreFactoryBean​​​​KeyStoreFactoryBean​​

    <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="password" value="password"/> <property name="location" value="classpath:org/springframework/ws/soap/security/xwss/test-keystore.jks"/></bean>

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

    KeyStoreCallbackHandler

    要使用 中的密钥库,您需要定义 .此回调有三个属性,类型分别为:(、 和 )。处理程序使用的确切存储取决于此处理程序要执行的加密操作。对于私钥操作,使用 。对于对称键操作,使用 。对于确定信任关系,使用 。下表对此表示:​​XwsSecurityInterceptor​​​​KeyStoreCallbackHandler​​​​keystore​​​​keyStore​​​​trustStore​​​​symmetricStore​​​​keyStore​​​​symmetricStore​​​​trustStore​​

    加密操作

    使用的密钥库

    证书验证

    首先,然后​​keyStore​​​​trustStore​​

    基于私钥解密

    ​​keyStore​​

    基于对称密钥的解密

    ​​symmetricStore​​

    基于公钥证书的加密

    ​​trustStore​​

    基于对称密钥的加密

    ​​symmetricStore​​

    签署

    ​​keyStore​​

    签名验证

    ​​trustStore​​

    此外,有一个属性,应将其设置为解锁“密钥存储”中包含的私钥。​​KeyStoreCallbackHandler​​​​privateKeyPassword​​

    如果未设置 ,则默认为 .如果未设置密钥或信任库,那么回调处理程序将使用标准 Java 机制来加载或创建它。请参阅 的 JavaDoc 以了解此机制的工作原理。​​symmetricStore​​​​keyStore​​​​KeyStoreCallbackHandler​​

    例如,如果要使用 来验证传入的证书或签名,则可以使用信任存储:​​KeyStoreCallbackHandler​​

    <beans> <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="trustStore" ref="trustStore"/> </bean> <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="classpath:truststore.jks"/> <property name="password" value="changeit"/> </bean></beans>

    如果要使用它来解密传入证书或对传出消息进行签名,可以使用密钥存储:

    <beans> <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="keyStore" ref="keyStore"/> <property name="privateKeyPassword" value="changeit"/> </bean> <bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="classpath:keystore.jks"/> <property name="password" value="changeit"/> </bean></beans>

    以下各节指示可以在何处使用 以及要为特定加密操作设置哪些属性。​​KeyStoreCallbackHandler​​

    7.1.2. 身份验证

    如本章引言中所述,身份验证是确定委托人是否是他们声称的身份的任务。在 WS-Security 中,身份验证可以采用两种形式:使用用户名和密码令牌(使用纯文本密码或密码摘要)或使用 X509 证书。

    纯文本用户名身份验证

    最简单的用户名身份验证形式使用纯文本密码。在这种情况下,SOAP 消息包含一个元素,该元素本身包含一个元素和一个包含纯文本密码的元素。纯文本身份验证可以与 HTTP 服务器提供的基本身份验证进行比较。​​UsernameToken​​​​Username​​​​Password​​

    请注意,纯文本密码不是很安全。因此,如果使用其他安全措施(例如,使用 HTTPS 而不是纯 HTTP),则应始终向传输层添加其他安全措施。

    要要求每封传入邮件都包含带有纯文本密码的 ,安全策略文件应包含一个元素,其属性设置为 。您可以在此处找到可能的子元素的参考。下面的清单显示了如何包含元素:​​UsernameToken​​​​RequireUsernameToken​​​​passwordDigestRequired​​​​false​​​​RequireUsernameToken​​

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

    如果用户名令牌不存在,则会向发送方返回 SOAP 错误。如果存在,它将向注册的处理程序触发带有 a 的 a。在 Spring-WS 中,有三个类来处理这个特定的回调。​​XwsSecurityInterceptor​​​​PasswordValidationCallback​​​​PlainTextPasswordRequest​​

    • SimplePasswordValidationCallbackHandler
    • SpringPlainTextPasswordValidationCallbackHandler
    • JaasPlainTextPasswordValidationCallbackHandler
    用​​SimplePasswordValidationCallbackHandler​​

    最简单的密码验证处理程序是 .此处理程序根据内存中对象验证密码,您可以使用以下属性指定该对象:​​SimplePasswordValidationCallbackHandler​​​​Properties​​​​users​​

    <bean id="passwordValidationHandler" class="org.springframework.ws.soap.security.xwss.callback.SimplePasswordValidationCallbackHandler"> <property name="users"> <props> <prop key="Bert">Ernie</prop> </props> </property></bean>

    在这种情况下,我们只允许用户“Bert”使用密码“Ernie”登录。

    用​​SpringPlainTextPasswordValidationCallbackHandler​​

    使用 Spring 安全性对用户进行身份验证。描述 Spring 安全性超出了本文档的范围,但它是一个成熟的安全框架。您可以在 Spring 安全性参考文档中阅读有关它的更多信息。​​SpringPlainTextPasswordValidationCallbackHandler​​

    需要操作。它使用此管理器对它创建的管理器进行身份验证。如果身份验证成功,令牌将存储在 .您可以使用以下属性设置身份验证管理器:​​SpringPlainTextPasswordValidationCallbackHandler​​​​AuthenticationManager​​​​UsernamePasswordAuthenticationToken​​​​SecurityContextHolder​​​​authenticationManager​​

    <beans> <bean id="springSecurityHandler" class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager"> <property name="providers"> <bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" /> ...</beans>
    用​​JaasPlainTextPasswordValidationCallbackHandler​​

    它基于标准的 Java 身份验证和授权服务。提供对 JAAS 的完整介绍超出了本文档的范围,但有一个很好的教程。​​JaasPlainTextPasswordValidationCallbackHandler​​

    只需要一个操作。它使用此名称并处理标准 JAAS 以及使用 SOAP 消息中提供的用户名和密码来创建新的 JAAS。这意味着此回调处理程序与在该阶段触发这些回调的任何 JAAS 集成,这是标准行为。​​JaasPlainTextPasswordValidationCallbackHandler​​​​loginContextName​​​​LoginContext​​​​NameCallback​​​​PasswordCallback​​​​LoginModule​​​​login()​​

    您可以按如下方式连接:​​JaasPlainTextPasswordValidationCallbackHandler​​

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

    在这种情况下,回调处理程序使用命名的 .此模块应在您的文件中定义,如前面提到的教程中所述。​​LoginContext​​​​MyLoginModule​​​​jaas.config​​

    摘要式用户名身份验证

    使用密码摘要时,SOAP 消息还包含一个元素,该元素本身包含一个元素和一个元素。不同之处在于密码不是以纯文本形式发送,而是以摘要形式发送。收件人将此摘要与他根据用户的已知密码计算出的摘要进行比较,如果它们相同,则对用户进行身份验证。此方法与 HTTP 服务器提供的摘要式身份验证相当。​​UsernameToken​​​​Username​​​​Password​​

    要要求每封传入邮件都包含一个带有密码摘要的元素,安全策略文件应包含一个元素,其属性设置为 。此外,该属性应设置为 : 您可以在此处找到可能的子元素的引用。下面的清单显示了如何定义元素:​​UsernameToken​​​​RequireUsernameToken​​​​passwordDigestRequired​​​​true​​​​nonceRequired​​​​true​​​​RequireUsernameToken​​

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

    如果用户名令牌不存在,则会向发送方返回 SOAP 错误。如果存在,它将向注册的处理程序触发带有 a 的 a。在 Spring-WS 中,有两个类处理这个特定的回调:和 .​​XwsSecurityInterceptor​​​​PasswordValidationCallback​​​​DigestPasswordRequest​​​​SimplePasswordValidationCallbackHandler​​​​SpringDigestPasswordValidationCallbackHandler​​

    用​​SimplePasswordValidationCallbackHandler​​

    可以处理纯文本密码以及密码摘要。它描述在使用 SimplePasswordValidationCallbackHandler 中。​​SimplePasswordValidationCallbackHandler​​

    用​​SpringDigestPasswordValidationCallbackHandler​​

    需要弹簧安全才能运行。它使用此服务检索令牌中指定的用户的密码。然后,将此详细信息对象中包含的密码摘要与消息中的摘要进行比较。如果它们相等,则用户已成功通过身份验证,并且 存储在 .可以使用属性设置服务。此外,还可以设置属性以缓存加载的用户详细信息。以下示例演示如何执行此操作:​​SpringDigestPasswordValidationCallbackHandler​​​​UserDetailService​​​​UsernamePasswordAuthenticationToken​​​​SecurityContextHolder​​​​userDetailsService​​​​userCache​​

    <beans> <bean class="org.springframework.ws.soap.security.xwss.callback.SpringDigestPasswordValidationCallbackHandler"> <property name="userDetailsService" ref="userDetailsService"/> </bean> <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" /> ...</beans>
    证书身份验证

    更安全的身份验证方式使用 X509 证书。在此方案中,SOAP 消息包含“二进制安全令牌”,其中包含 X509 证书的 Base 64 编码版本。收件人使用该证书进行身份验证。存储在消息中的证书也用于对消息进行签名(请参阅验证签名)。

    为了确保所有传入的 SOAP 消息都带有“二进制安全令牌”,安全策略文件应包含一个元素。此元素可以进一步携带验证签名中介绍的其他元素。您可以在此处找到可能的子元素的参考。下面的清单显示了如何定义元素:​​RequireSignature​​​​RequireSignature​​

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

    当未携带证书的消息到达时,会向发件人返回 SOAP 错误。如果存在,它将触发 .Spring-WS 中的三个处理程序出于身份验证目的处理此回调:​​XwsSecurityInterceptor​​​​CertificateValidationCallback​​

    • KeyStoreCallbackHandler
    • SpringCertificateValidationCallbackHandler
    • JaasCertificateValidationCallbackHandler

    在大多数情况下,证书身份验证之前应先进行证书验证,因为您只想针对有效证书进行身份验证。应忽略无效证书,例如过期日期已过或不在受信任证书存储中的证书。

    在 Spring-WS 术语中,这意味着 or 前面应该有 .这可以通过在以下配置中设置属性的顺序来实现:​​SpringCertificateValidationCallbackHandler​​​​JaasCertificateValidationCallbackHandler​​​​KeyStoreCallbackHandler​​​​callbackHandlers​​​​XwsSecurityInterceptor​​

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

    使用此设置,拦截器首先确定消息中的证书是否有效,然后针对密钥库进行身份验证。

    用​​KeyStoreCallbackHandler​​

    使用标准 Java 密钥库来验证证书。此证书验证过程包括以下步骤:。​​KeyStoreCallbackHandler​​

  • 处理程序检查证书是否在私有 .如果是,则有效。keyStore
  • 如果证书不在私有密钥库中,处理程序将检查当前日期和时间是否在证书中给定的有效期限内。如果不是,则证书无效。如果是,则继续执行最后一步。
  • 将创建证书的证书路径。这基本上意味着处理程序确定证书是否已由 中的任何证书颁发机构颁发。如果可以成功构建证书路径,则该证书有效。否则,证书无效。trustStore
  • 若要将 用于证书验证目的,很可能只需要设置以下属性:​​KeyStoreCallbackHandler​​​​trustStore​​

    <beans> <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="trustStore" ref="trustStore"/> </bean> <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="classpath:truststore.jks"/> <property name="password" value="changeit"/> </bean></beans>

    使用前面示例中所示的设置,要验证的证书必须位于信任存储区本身中,或者信任库必须包含颁发证书的证书颁发机构。

    用​​SpringCertificateValidationCallbackHandler​​

    需要弹簧安全才能运行。它使用此管理器对它创建的管理器进行身份验证。配置的身份验证管理器应提供可处理此令牌的提供程序(通常是 的实例)。如果身份验证成功,令牌将存储在 .您可以使用以下属性设置身份验证管理器:​​SpringCertificateValidationCallbackHandler​​​​AuthenticationManager​​​​X509AuthenticationToken​​​​X509AuthenticationProvider​​​​SecurityContextHolder​​​​authenticationManager​​

    <beans> <bean id="springSecurityCertificateHandler" class="org.springframework.ws.soap.security.xwss.callback.SpringCertificateValidationCallbackHandler"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <bean id="authenticationManager" class="org.springframework.security.providers.ProviderManager"> <property name="providers"> <bean class="org.springframework.ws.soap.security.x509.X509AuthenticationProvider"> <property name="x509AuthoritiesPopulator"> <bean class="org.springframework.ws.soap.security.x509.populator.DaoX509AuthoritiesPopulator"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> </property> </bean> <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" /> ...</beans>

    在这种情况下,我们使用自定义用户详细信息服务来获取基于证书的身份验证详细信息。有关针对 X509 证书进行身份验证的更多信息,请参阅 Spring 安全性参考文档。

    用​​JaasCertificateValidationCallbackHandler​​

    需要 a 才能操作。它使用此名称和证书的 创建新的 JAAS。这意味着此回调处理程序与处理 X500 主体的任何 JAAS 集成。​​JaasCertificateValidationCallbackHandler​​​​loginContextName​​​​LoginContext​​​​X500Principal​​​​LoginModule​​

    您可以按如下方式连接:​​JaasCertificateValidationCallbackHandler​​

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

    在这种情况下,回调处理程序使用命名的 .此模块应在文件中定义,并且应该能够针对 X500 主体进行身份验证。​​LoginContext​​​​MyLoginModule​​​​jaas.config​​

    7.1.3. 数字签名

    邮件的数字签名是基于文档和签名者私钥的一条信息。WS-Security 中的签名有两个主要任务:验证签名和对消息进行签名。

    验证签名

    与基于证书的身份验证一样,签名消息包含 ,其中包含用于对消息进行签名的证书。此外,它还包含一个块,指示对消息的哪一部分进行了签名。​​BinarySecurityToken​​​​SignedInfo​​

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

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

    如果签名不存在,则 将向发送方返回 SOAP 错误。如果存在,它将向已注册的处理程序触发 a。在 Spring-WS 中,有一个类处理这个特定的回调:。​​XwsSecurityInterceptor​​​​SignatureVerificationKeyCallback​​​​KeyStoreCallbackHandler​​

    用​​KeyStoreCallbackHandler​​

    如 KeyStoreCallbackHandler 中所述,使用 a 来处理各种加密回调,包括签名验证。对于签名验证,处理程序使用以下属性:​​KeyStoreCallbackHandler​​​​java.security.KeyStore​​​​trustStore​​

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

    对消息进行签名时,会将 添加到消息中。它还添加一个块,指示对消息的哪一部分进行了签名。​​XwsSecurityInterceptor​​​​BinarySecurityToken​​​​SignedInfo​​

    若要对所有传出 SOAP 消息进行签名,安全策略文件应包含一个元素。它还可以包含一个元素,该元素指定预期要签名的目标消息部分和各种其他子元素。您还可以定义要使用的私钥别名、是否使用对称密钥而不是私钥以及许多其他属性。您可以在此处找到可能的子元素的参考。以下示例包含一个元素:​​Sign​​​​SignatureTarget​​​​Sign​​

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

    向注册的处理程序触发 a。在 Spring-WS 中,该类处理此特定回调。​​XwsSecurityInterceptor​​​​SignatureKeyCallback​​​​KeyStoreCallbackHandler​​

    用​​KeyStoreCallbackHandler​​

    如 KeyStoreCallbackHandler 中所述,使用 a 来处理各种加密回调,包括签名消息。对于添加签名,处理程序使用该属性。此外,必须设置属性以解锁用于签名的私钥。以下示例使用:​​KeyStoreCallbackHandler​​​​java.security.KeyStore​​​​keyStore​​​​privateKeyPassword​​​​KeyStoreCallbackHandler​​

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

    加密时,消息将转换为只能使用适当密钥读取的形式。可以解密消息以显示原始的可读消息。

    解密

    若要解密传入的 SOAP 消息,安全策略文件应包含一个元素。此元素可以进一步携带一个元素,该元素指示应加密消息的哪个部分,以及指示应使用共享密钥而不是常规私钥来解密消息的元素。您可以​​在此处​​阅读其他元素的说明。以下示例使用元素:​​RequireEncryption​​​​EncryptionTarget​​​​SymmetricKey​​​​RequireEncryption​​

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

    如果传入消息未加密,则 会向发送方返回 SOAP ault。如果存在,它将向已注册的处理程序触发 a。在 Spring-WS 中,该类处理此特定回调。​​XwsSecurityInterceptor​​​​DecryptionKeyCallback​​​​KeyStoreCallbackHandler​​

    用​​KeyStoreCallbackHandler​​

    如 KeyStoreCallbackHandler 中所述,使用 a 来处理各种加密回调,包括解密。对于解密,处理程序使用该属性。此外,必须设置该属性以解锁用于解密的私钥。对于基于对称密钥的解密,它使用 .以下示例使用:​​KeyStoreCallbackHandler​​​​java.security.KeyStore​​​​keyStore​​​​privateKeyPassword​​​​symmetricStore​​​​KeyStoreCallbackHandler​​

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

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

    触发 an 到注册的处理程序以检索加密信息。在 Spring-WS 中,该类处理此特定回调。​​XwsSecurityInterceptor​​​​EncryptionKeyCallback​​​​KeyStoreCallbackHandler​​

    用​​KeyStoreCallbackHandler​​

    如 KeyStoreCallbackHandler 中所述,使用 a 来处理各种加密回调,包括加密。对于基于公钥的加密,处理程序使用该属性。对于基于对称密钥的加密,它使用 .以下示例使用:​​KeyStoreCallbackHandler​​​​java.security.KeyStore​​​​trustStore​​​​symmetricStore​​​​KeyStoreCallbackHandler​​

    <beans> <bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler"> <property name="trustStore" ref="trustStore"/> </bean> <bean id="trustStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="classpath:truststore.jks"/> <property name="password" value="changeit"/> </bean></beans>

    7.1.5. 安全异常处理

    当安全或验证操作失败时,将分别抛出 或。这些异常绕过标准异常处理机制,但由侦听器本身处理。​​XwsSecurityInterceptor​​​​WsSecuritySecurementException​​​​WsSecurityValidationException​​

    ​​WsSecuritySecurementException​​异常由 的方法处理。默认情况下,此方法记录错误并停止对消息的进一步处理。​​handleSecurementException​​​​XwsSecurityInterceptor​​

    同样,异常由 的方法处理。默认情况下,此方法创建 SOAP 1.1 客户端或 SOAP 1.2 发送方错误,并将其作为响应发送回。​​WsSecurityValidationException​​​​handleValidationException​​​​XwsSecurityInterceptor​​

    两者都是受保护的方法,您可以重写这些方法以更改其默认行为。​​handleSecurementException​​​​handleValidationException​​

    7.2. 使用​​Wss4jSecurityInterceptor​​

    这是一个基于Apache的WSS4J的(参见拦截请求 — 端点拦截器接口)。​​Wss4jSecurityInterceptor​​​​EndpointInterceptor​​

    WSS4J 实现以下标准:

    • OASIS Web 服务安全性:SOAP 消息安全 1.0 标准 200401,2004 年 3 月
    • 用户名令牌配置文件 V1.0
    • X.509 令牌配置文件 V1.0

    此侦听器支持由 和 创建的消息。​​AxiomSoapMessageFactory​​​​SaajSoapMessageFactory​​

    7.2.1. 配置​​Wss4jSecurityInterceptor​​

    WSS4J 不使用外部配置文件。拦截器完全由属性配置。此侦听器调用的验证和安全操作分别通过 and 属性指定。操作作为空格分隔的字符串传递。以下清单显示了一个示例配置:​​validationActions​​​​securementActions​​

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

    下表显示了可用的验证操作:

    验证操作

    描述

    ​​UsernameToken​​

    验证用户名令牌

    ​​Timestamp​​

    验证时间戳

    ​​Encrypt​​

    解密消息

    ​​Signature​​

    验证签名

    ​​NoSecurity​​

    未执行任何操作

    下表显示了可用的安全操作:

    担保行动

    描述

    ​​UsernameToken​​

    添加用户名令牌

    ​​UsernameTokenSignature​​

    添加用户名令牌和签名用户名令牌密钥

    ​​Timestamp​​

    添加时间戳

    ​​Encrypt​​

    加密响应

    ​​Signature​​

    对响应进行签名

    ​​NoSecurity​​

    未执行任何操作

    操作的顺序很重要,由拦截器强制执行。如果其安全操作的执行顺序与“validationActions”指定的顺序不同,则拦截器将拒绝传入的 SOAP 消息。

    7.2.2. 处理数字证书

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

    ​​Crypto​​实例可以从WSS4J获得,也可以使用Spring-WS'CryptoFactoryBean'更方便地获得。​​CryptoFactory​​

    加密工厂豆

    Spring-WS 提供了一个方便的工厂 bean,它通过强类型属性(首选)或通过对象构造和配置实例。​​CryptoFactoryBean​​​​Crypto​​​​Properties​​

    缺省情况下, 返回 的实例。可以通过设置属性(或其等效的字符串属性)来更改此设置。​​CryptoFactoryBean​​​​org.apache.ws.security.components.crypto.Merlin​​​​cryptoProvider​​​​org.apache.ws.security.crypto.provider​​

    以下示例配置使用:​​CryptoFactoryBean​​

    <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-WS提供了一组回调处理程序来与Spring Security集成。此外,还提供了一个简单的回调处理程序 ,用于使用内存中对象配置用户和密码。​​SimplePasswordValidationCallbackHandler​​​​Properties​​

    回调处理程序是通过属性配置的。​​validationCallbackHandler​​​​Wss4jSecurityInterceptor​​

    用​​SimplePasswordValidationCallbackHandler​​

    ​​SimplePasswordValidationCallbackHandler​​根据内存中对象验证纯文本和摘要用户名令牌。您可以按如下方式对其进行配置:​​Properties​​

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

    通过使用 Spring 安全性来验证纯文本和摘要密码进行操作。它使用此服务检索令牌中指定的用户的密码(或密码摘要)。然后,将此详细信息对象中包含的密码(或密码摘要)与消息中的摘要进行比较。如果它们相等,则用户已成功进行身份验证,并且 a 存储在“SecurityContextHolder”中。可以使用 来设置服务。此外,还可以设置一个属性来缓存加载的用户详细信息,如下所示:​​SpringSecurityPasswordValidationCallbackHandler​​​​UserDetailService​​​​UsernamePasswordAuthenticationToken​​​​userDetailsService​​​​userCache​​

    <beans> <bean class="org.springframework.ws.soap.security.wss4j.callback.SpringDigestPasswordValidationCallbackHandler"> <property name="userDetailsService" ref="userDetailsService"/> </bean> <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" /> ...</beans>
    添加用户名令牌

    将用户名令牌添加到传出消息就像添加到 的属性并指定 和 'securementPassword' 一样简单。​​UsernameToken​​​​securementActions​​​​Wss4jSecurityInterceptor​​​​securementUsername​​

    可以通过设置属性来设置密码类型。可能的值适用于纯文本密码或摘要密码,这是默认值。​​securementPasswordType​​​​PasswordText​​​​PasswordDigest​​

    以下示例使用摘要密码生成用户名令牌:

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="securementActions" value="UsernameToken"/> <property name="securementUsername" value="Ernie"/> <property name="securementPassword" value="Bert"/></bean>

    如果选择纯文本密码类型,则可以通过设置属性来指示拦截器添加和元素。该值必须是包含所需元素名称的列表,这些名称以空格分隔(区分大小写)。​​Nonce​​​​Created​​​​securementUsernameTokenElements​​

    以下示例生成一个包含纯文本密码、a 和元素的用户名令牌:​​Nonce​​​​Created​​

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

    由于证书身份验证类似于数字签名,因此 WSS4J 将其作为签名验证和保护的一部分进行处理。具体而言,必须将该属性设置为 才能指示 WSS4J 生成包含 X509 证书的元素并将其包含在传出消息中。证书的名称和密码分别通过 and 属性传递,如以下示例所示:​​securementSignatureKeyIdentifier​​​​DirectReference​​​​BinarySecurityToken​​​​securementUsername​​​​securementPassword​​

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="securementActions" value="Signature"/> <property name="securementSignatureKeyIdentifier" value="DirectReference"/> <property name="securementUsername" value="mycert"/> <property name="securementPassword" value="certpass"/> <property name="securementSignatureCrypto"> <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean"> <property name="keyStorePassword" value="123456"/> <property name="keyStoreLocation" value="classpath:/keystore.jks"/> </bean> </property></bean>

    对于证书验证,常规签名验证适用:

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

    在验证结束时,拦截器通过委派给默认的 WSS4J 实现来自动验证证书的有效性。如果需要,可以通过重新定义方法来更改此行为。​​verifyCertificateTrust​​

    有关更多详细信息,请参阅数字签名。

    7.2.4. 安全时间戳

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

    验证时间戳

    若要验证时间戳,请添加到属性。可以通过设置属性来重写 SOAP 消息发起方指定的时间戳语义,方法是设置服务器端生存时间(以秒为单位)(默认值:300)。拦截器始终拒绝已过期的时间戳,无论其值是多少。​​Timestamp​​​​validationActions​​​​timestampStrict​​​​true​​​​timeToLive​​​​timeToLive​​

    在以下示例中,拦截器将时间戳有效性窗口限制为 10 秒,拒绝该窗口之外的任何有效时间戳令牌:

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

    添加到属性会在传出消息中生成时间戳标头。该属性指定生成的时间戳的精度是否以毫秒为单位。默认值为 。以下清单添加时间戳:​​Timestamp​​​​securementActions​​​​timestampPrecisionInMilliseconds​​​​true​​

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

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

    验证签名

    要指示 ,必须包含操作。此外,该属性必须指向包含启动器的公共证书的密钥库:​​Wss4jSecurityInterceptor​​​​validationActions​​​​Signature​​​​validationSignatureCrypto​​

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

    通过将操作添加到 .要使用的私钥的别名和密码分别由 和 属性指定。 必须指向包含专用密钥的密钥库:​​Signature​​​​securementActions​​​​securementUsername​​​​securementPassword​​​​securementSignatureCrypto​​

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="securementActions" value="Signature"/> <property name="securementUsername" value="mykey"/> <property name="securementPassword" value="123456"/> <property name="securementSignatureCrypto"> <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean"> <property name="keyStorePassword" value="123456"/> <property name="keyStoreLocation" value="classpath:/keystore.jks"/> </bean> </property></bean>

    此外,您可以通过设置属性来定义签名算法。​​securementSignatureAlgorithm​​

    可以通过设置属性来自定义要使用的密钥标识符类型。仅 并且对签名有效。​​securementSignatureKeyIdentifier​​​​IssuerSerial​​​​DirectReference​​

    该属性控制对消息的哪一部分进行签名。此属性的值是以分号分隔的元素名称的列表,用于标识要签名的元素。签名部件的一般形式是 。请注意,第一个空括号仅用于加密部分。默认行为是对 SOAP 正文进行签名。​​securementSignatureParts​​​​{}{namespace}Element​​

    下面的示例演示如何对 Spring Web 服务回显示例中的元素进行签名:​​echoResponse​​

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

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

    如果请求中没有其他元素具有本地名称 ,则 SOAP 命名空间标识符可以为空 ()。​​Body​​​​{}​​

    签名确认

    通过设置为 来启用签名确认。请注意,签名确认操作跨越请求和响应。这意味着 必须将 和 设置为(默认值),即使没有相应的安全操作。下面的示例将属性设置为 :​​enableSignatureConfirmation​​​​true​​​​secureResponse​​​​validateRequest​​​​true​​​​enableSignatureConfirmation​​​​true​​

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="validationActions" value="Signature"/> <property name="enableSignatureConfirmation" value="true"/> <property name="validationSignatureCrypto"> <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean"> <property name="keyStorePassword" value="123456"/> <property name="keyStoreLocation" value="file:/keystore.jks"/> </bean> </property></bean>
    7.2.6. 解密和加密

    本节介绍 中提供的各种解密和加密选项。​​Wss4jSecurityInterceptor​​

    解密

    解密传入的 SOAP 消息需要将操作添加到属性中。其余配置取决于消息中显示的关键信息。(这是因为 WSS4J 只需要加密密钥的 Crypto,而嵌入式密钥名称验证委托给回调处理程序。​​Encrypt​​​​validationActions​​

    要使用嵌入式加密对称密钥(元素)解密消息,需要指向包含解密私钥的密钥库。此外,必须注入指定密钥密码的:​​xenc:EncryptedKey​​​​validationDecryptionCrypto​​​​validationCallbackHandler​​​​org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler​​

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="validationActions" value="Encrypt"/> <property name="validationDecryptionCrypto"> <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean"> <property name="keyStorePassword" value="123456"/> <property name="keyStoreLocation" value="classpath:/keystore.jks"/> </bean> </property> <property name="validationCallbackHandler"> <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler"> <property name="privateKeyPassword" value="mykeypass"/> </bean> </property></bean>

    要支持解密具有嵌入式密钥名称(元素)的消息,您可以配置指向具有对称密钥密钥库的 。该属性指示密钥的密码,密钥名称由元素指定:​​ds:KeyName​​​​KeyStoreCallbackHandler​​​​symmetricKeyPassword​​​​ds:KeyName​​

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

    添加到启用传出邮件的加密。可以通过设置属性来设置证书的别名以用于加密。证书所在的密钥库可通过属性进行访问。由于加密依赖于公共证书,因此无需传递密码。下面的示例使用该属性:​​Encrypt​​​​securementActions​​​​securementEncryptionUser​​​​securementEncryptionCrypto​​​​securementEncryptionCrypto​​

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="securementActions" value="Encrypt"/> <property name="securementEncryptionUser" value="mycert"/> <property name="securementEncryptionCrypto"> <bean class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean"> <property name="keyStorePassword" value="123456"/> <property name="keyStoreLocation" value="file:/keystore.jks"/> </bean> </property></bean>

    可以通过多种方式自定义加密: 要使用的密钥标识符类型由属性定义。可能的值为 ,, ,, , 和 。​​securementEncryptionKeyIdentifier​​​​IssuerSerial​​​​X509KeyIdentifier​​​​DirectReference​​​​Thumbprint​​​​SKIKeyIdentifier​​​​EmbeddedKeyName​​

    如果选择类型,则需要指定用于加密的密钥。键的别名在属性中设置,与其他键标识符类型一样。但是,WSS4J 需要一个回调处理程序来获取密钥。因此,您必须提供指向相应密钥库的 。默认情况下,生成的 WS-Security 标头中的元素采用属性的值。要指示其他名称,您可以设置所需的值。在下一个示例中,传出消息使用别名密钥进行加密,而出现在元素中:​​EmbeddedKeyName​​​​securementEncryptionUser​​​​securementCallbackHandler​​​​KeyStoreCallbackHandler​​​​ds:KeyName​​​​securementEncryptionUser​​​​securementEncryptionEmbeddedKeyName​​​​secretKey​​​​myKey​​​​ds:KeyName​​

    <bean class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor"> <property name="securementActions" value="Encrypt"/> <property name="securementEncryptionKeyIdentifier" value="EmbeddedKeyName"/> <property name="securementEncryptionUser" value="secretKey"/> <property name="securementEncryptionEmbeddedKeyName" value="myKey"/> <property name="securementCallbackHandler"> <bean class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler"> <property name="symmetricKeyPassword" value="keypass"/> <property name="keyStore"> <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean"> <property name="location" value="file:/keystore.jks"/> <property name="type" value="jceks"/> <property name="password" value="123456"/> </bean> </property> </bean> </property></bean>

    该属性定义用于加密生成的对称密钥的算法。支持的值为 、(默认值)和 。​​securementEncryptionKeyTransportAlgorithm​​​​http://www.w3.org/2001/04/xmlenc#rsa-1_5​​​​http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p​​

    可以通过设置属性来设置要使用的对称加密算法。支持的值为(默认值)、、 和 。​​securementEncryptionSymAlgorithm​​​​http://www.w3.org/2001/04/xmlenc#aes128-cbc​​​​http://www.w3.org/2001/04/xmlenc#tripledes-cbc​​​​http://www.w3.org/2001/04/xmlenc#aes256-cbc​​​​http://www.w3.org/2001/04/xmlenc#aes192-cbc​​

    最后,该属性定义对消息的哪些部分进行加密。此属性的值是以分号分隔的元素名称的列表,用于标识要加密的元素。加密模式说明符和命名空间标识(每个都位于一对大括号内)可以位于每个元素名称之前。加密模式说明符是 或 请参阅 W3C XML 加密规范,了解元素加密和内容加密之间的差异。以下示例标识来自回显示例的 :​​securementEncryptionParts​​​​{Content}​​​​{Element}​​​​echoResponse​​

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

    请注意,元素名称、命名空间标识符和加密修饰符区分大小写。可以省略加密修饰符和命名空间标识符。如果这样做,加密模式默认为 ,并且命名空间将设置为 SOAP 命名空间。​​Content​​

    若要指定没有命名空间的元素,请使用值(区分大小写)作为命名空间名称。如果未指定列表,则处理程序默认在模式下加密 SOAP 正文。​​Null​​​​Content​​

    7.2.7. 安全异常处理

    的异常处理与 的异常处理相同。有关详细信息,请参阅安全异常处理。​​Wss4jSecurityInterceptor​​​​XwsSecurityInterceptor​​

    三、其他资源

    除了这个参考文档之外,许多其他资源可以帮助你学习如何使用 Spring Web 服务。本节列举了这些额外的第三方资源。

    书目

    • [沃尔多-94]Jim Waldo,Ann Wollrath和Sam Kendall。关于分布式计算的说明。施普林格出版社。1994
    • [高山]Steve Loughran & Edmund Smith。重新思考Java SOAP Stack。2005 年 5 月 17 日。© 2005 IEEE电话实验室公司
    • [有效企业-Java]泰德·纽瓦德。斯科特·迈耶斯。有效的企业Java。艾迪生-韦斯利。2004
    • [有效-XML]艾略特·鲁斯蒂·哈罗德。斯科特·迈耶斯。有效的 XML。艾迪生-韦斯利。2004
    上一篇:如何构建一个经典的“Hello World!”端点
    下一篇:没有了
    网友评论