通过 WebLogic Server 9.2 保护 Web 服务
页面: 1, 2, 3

为这个 Hello World 服务构建一个简单的客户端并进行测试

我们现在要构建一个简单的 Java 客户端来测试刚刚创建的服务。尽管可以用 WebLogic Server 控制台工具测试该服务,但最终还是需要用客户端来测试其各安全性方面。

回想一下,客户端要成功地调用服务,首先必须为该服务生成客户端代理。这可通过使用另一个 Ant 脚本来完成。在 WORKSPACE_DIR/WSTest 文件夹中,创建一个名为 gen-client.xml 的新文本文件,并将其内容设置为如下所示:

 

<project default="build-client">

      <taskdef name="clientgen"

        classname="weblogic.wsee.tools.anttasks.ClientGenTask"<//>

<target name="build-client">

<clientgen

wsdl="HelloWorldService.wsdl"

destDir="."

packageName="com.test.client"/>

</target>

</project>



在前面打开的命令 shell 窗口中,运行 ant -buildfile gen-client.xml 命令。您应该会看到一条 BUILD SUCCESSFUL 消息。这将生成客户端代理。

回到 WTP,刷新 WSTest 项目。应该能看到新生成的 com.test.client 程序包。然而,WTP 会报告新生成的代理存在一个错误。要修正它,就要为项目添加 webserviceclient.jar 库,添加方法与添加 weblogic.jar 相同( webserviceclient.jarweblogic.jar 位于同一目录中)。我们现在就可以开始编写客户端代码了。

在 WTP 的 WSTest 项目中,创建一个名为 com.test.client.HelloWorldClient 的新 java 类。将其源代码设置为如下所示:

 

package com.test;



import com.test.client.*;



public class HelloWorldClient {

 public static void main(String[] args) throws Throwable {

  com.test.HelloWorldService service = new HelloWorldService_Impl();

  HelloWorldPortType port = service.getHelloWorldPortTypeSoapPort();

  String greeting = port.sayHello("Gary");

  System.out.println("The greeting returned was: " + greeting);

  }

}





这个客户端非常简单,仅利用客户端代理类以获得对服务的引用,然后在其上调用 sayHello 方法。

运行这段代码,会出现适当的控制台打印结果。

返回的问候是:Hello there, Gary

请注意,尽管我们已经激活了 TCP/IP 监视器,但这个测试还不会用到它。这是因为客户端在端口 7001 直接找到服务器,而不是通过受监视的端口 7002。我们稍后将介绍如何更改它。

现在已经构建了非常简单的 Web 服务和客户端,但还不具备安全性机制。下面将加以介绍。

生成客户端密钥

我们需要一组密钥来实现各种形式的消息完整性(和加密)。密钥构成 证书 的基础,证书用来唯一地对一条消息进行 签名 以保证消息的完整性。发送方可以在消息中附加自己的证书;接收方可以检查证书,核对发送方身份,确定消息在传输中未被篡改。

客户端也需要随证书发送数字签名增加消息完整性。单独的证书本身是没有意义的。客户端获得消息的散列,然后用专用密钥加密它,从而生成数字签名。然后,服务器使用消息中附加的客户证书(即,公用密钥)解密这个散列,证明它来自客户端。接下来,对比解密的散列和得到的消息散列。如果二者等同,则证明消息未被篡改。本教程有两个通信方:WebLogic Server 和客户端。双方都需要密钥。

WebLogic Server 有自己的示范用的“虚拟”密钥,虽然不能用于生产,但是本教程用其进行示范就足够了。这里我们将使用这些虚拟密钥,但请记住,在实际环境中应该使用新生成的密钥。我们马上要用到 keytool 命令为客户端和服务器创建密钥。

现在来创建客户端密钥。具体来说,我们将为客户端创建密钥对(公钥和相关联的私钥)。密钥对中的私钥将用作消息的解密和数字签名。密钥对保存在密钥存储器文件中。密钥存储器随密钥对的创建而创建。如果指定的密钥存储器已存在,则只需要将新的密钥对添加进去。公钥包装在 X.509 证书 内。证书和私钥保存在密钥存储器中,用一个别名来识别。

打开一个新的命令 shell,键入 cd 进入 BEA JRE bin 目录(通常类似于 C:\bea\jdk150_04\jre\bin)。然后键入以下命令:

keytool -genkey -keyalg RSA -keystore C:\client_keystore.jks -storepass abc123 -alias client_key -keypass client_key_password -dname "CN=Client, OU=WEB AGE, C=US" -keysize 1024 -validity 1460

(此命令将在根目录下创建一个密钥存储器。根据需要,可以改变该密钥存储器的位置。 如果您使用的是基于 Unix 的操作系统,可以用更标准的 Unix 命名文件系统规则代替“C:\”。)

创建的密钥存储器名称是 C:\client_keystore.jks,用于访问该密钥存储器的口令是 abc123。密钥对的别名是 client_key,私钥的保护口令是 client_key_password。用于创建密钥对的算法是 RSA。与别名 client_key 相关联的识别名称是 CN=Client, OU=WEB AGE, C=US,在证书中用作颁发者和主题域。密钥长度为 1024 位(BEA 要求的最小长度)。最后,对应的证书有效期为 1460天(4 年)。 如果成功,C:\ 下应该出现一个名为 client_keystore.jks 的新文件。这就是客户端要用到的密钥存储器。

现在,我们将要检验是否正确地创建了密钥。在命令提示符窗口中,键入:

keytool -list -keystore C:\client_keystore.jks -storepass abc123 -v | findstr Alias

命令应显示:

Alias name:client_key

密钥存储器已经成功地创建了。客户端密钥存储器包含一个名为 client_key 的实体的私钥和一个公钥(也称为证书)。发送数字签名和加密都需要它们。

请勿关闭第二个命令 shell 窗口。稍后还要使用它。

将客户端证书导入服务器信任文件

我们现在必须告诉服务器信任客户端的证书。

WebLogic Server 维护着一个信任文件,实质上就是它要信任和接受的证书列表。遗憾的是,我们前面生成的客户端密钥并不包括在该信任文件中。这意味着如果 WebLogic 收到我们的客户端证书,就会立刻拒绝它。我们需要做的是将客户端证书导入服务器信任文件中。

这个过程分两个阶段:我们首先必须从客户端的密钥存储器导出证书,然后将其导入服务器的信任文件。服务器当前配置为使用一个“虚拟”信任文件 ( Demotrust.jks),它又引用标准 JDK 信任文件。标准 JDK 信任文件一般位于:

C:\bea\jdk150_04\jre\lib\security\cacerts。(注意,根据您的操作系统和安装设置不同,这个目录可能会有不同。)

我们需要将客户端证书导入这个 cacerts 文件。

首先,我们将从 client_keystore.jks 导出客户端证书。在生成密钥时打开的命令 shell 窗口中,键入以下命令:

keytool -export -alias client_key -file client_cert.der -keystore C:\client_keystore.jks -storepass abc123

下一步是将证书导入服务器信任文件。输入以下命令:

keytool -import -alias client_key -file client_cert.der -keystore C:\bea\JDK150~1\jre\lib\security\cacerts

(如果您的 cacerts 文件不是位于 C:\bea\JDK150~1\jre\lib\security\,则相应地修改命令。)

出现提示后输入口令 changeit。( changeit 是 WebLogic Server 附带的默认口令。当然,在生产中应当换成更安全的口令。)

接着会出现提示 Trust this certificate [no]:。键入 y,按 Enter 键。

如果一切顺利,您将看到 Certificate was added to keystore 消息。

此时,客户端的证书已经添加至服务器信任文件。现在服务器会信任使用该证书的任何客户端。

关闭运行 keytool 命令的命令 shell 窗口。

为服务添加完整性

既然有了客户端密钥,现在来看看它将如何帮助我们实现消息完整性。如果一方发送 SOAP 消息并在传出消息中附加证书,则接收方可以阅读证书,自信地识别发送方,并确保消息在传输中未被篡改。这是通过客户端随 SOAP 请求发送自己的证书实现的。(回想一下,传输的证书也有一个消息的散列摘要。被篡改后,消息的散列将有所不同,服务器能检测出来。这就保证了消息的完整性。)

为了便于实现,我们必须进行两项更改:

  • 必须编辑服务类,以便请求签名。
  • 必须编辑客户类,以便在请求中附加签名。

使服务请求消息完整性非常简单,只需要向源代码添加一个单独的批注。切换回 WTP,打开 HelloWorldService.java。添加以下 突出显示的代码:

import javax.jws.WebService;



                         
import weblogic.jws.Policies;

import weblogic.jws.Policy;
@WebService(name = "HelloWorldPortType", serviceName = "HelloWorldService", targetNamespace = "http://mycompany.com")
@Policies({

@Policy(uri="policy:Sign.xml")

})
public class HelloWorldService { public String sayHello(String name) { return "Hello there, " + name; } }

 我们所作的一切就是添加了一个单独的批注 ( @Policies/@Policy) 并导入了所需的适当程序包。该批注将确保生成的 Web 服务始终要求客户端对其 SOAP 请求进行签名。保存更改。这部分应该不会出现错误。

现在需要重新生成服务。使用前面生成服务而打开的命令 shell,运行 ant 命令。您将看到 BUILD SUCCESSFUL 消息;这将生成、打包和部署服务。

由于更改了服务,我们还要基于新生成的 WSDL 文件重新生成客户端代理。返回浏览器,重新打开 WSDL 文件的 URL。将它保存为 WORKSPACE_DIR\WSTest\HelloWorldService.wsdl。用文本编辑器检查该文件。注意,现在其中包含一个名为 wssp:Integrity 的元素,它包含了相当多的信息,其中有它信任的内容。如果您搜索 wssp:TokenIssuer 元素体,应该看见一个对 CN=Client, OU=WEB AGE, C=US 的引用,它确实是我们的客户端密钥中生成的,之后导入 WebLogic Server 信任文件的信息。另请注意 WSDL 文件中包括的新的 Sign.xml 策略:

<wsp:Policy s0:Id="Sign.xml"> 

...

</wsp:Policy> 

...

  <portType name="HelloWorldPortType" wsp:PolicyURIs="#Sign.xml">
                        


    ..
                        


  </portType>

                      

WSDL 文件末尾处有如下代码:

<s2:address location="http://localhost:7001/HelloWorldService/HelloWorldService"<//>

将端口号更改为 7002,如下所示:

<s2:address location="http://localhost: 7002/HelloWorldService/HelloWorldService"<//>

为何要这样做?原因很简单。我们希望客户端请求找到端口 7002 — 监视器端口。保存并关闭 WSDL 文件。

从命令 shell 窗口运行 ant -buildfile gen-client.xml 命令。您应该看到熟悉的 BUILD SUCCESSFUL 消息。现在重新生成了客户端代理。

返回 WTP,刷新 WSTest 项目。再次运行 HelloWorldClient 类。发生了什么?一个异常。检查出现的堆栈跟踪,找到 Failed to add Signature. 消息。这说明服务要求证书,但是客户端没有附上。我们要更改客户端代码,以便调用服务时确实能够附上证书。

在 WTP 中打开 HelloWorldClient.java。添加以下 import 语句。

import java.security.cert.X509Certificate;

import java.util.ArrayList;

import java.util.List;



import javax.xml.rpc.Stub;



import weblogic.security.SSL.TrustManager;

import weblogic.wsee.security.bst.ClientBSTCredentialProvider;

import weblogic.wsee.security.unt.ClientUNTCredentialProvider;

import weblogic.xml.crypto.wss.WSSecurityContext;

import weblogic.xml.crypto.wss.provider.CredentialProvider;

现在,向 main 方法添加以下 突出显示的代码。

public static void main(String[] args) throws Throwable {

 com.test.client.HelloWorldService service = new HelloWorldService_Impl();

 HelloWorldPortType port = service.getHelloWorldPortTypeSoapPort();



  
                        
List credProviders = new ArrayList();

CredentialProvider cp = new ClientBSTCredentialProvider(

"C:\\client_keystore.jks", "abc123", "client_key",

"client_key_password");

credProviders.add(cp);

Stub stub = (Stub) port;

stub._setProperty(

WSSecurityContext.CREDENTIAL_PROVIDER_LIST,

credProviders);

stub._setProperty(

WSSecurityContext.TRUST_MANAGER, new TrustManager() {

public boolean certificateCallback(X509Certificate[] chain,

int validateErr) {

// Put some custom validation code in here.

// Just return true for now

return true;

}

});
String greeting = port.sayHello("Gary"); System.out.println("The greeting returned was: " + greeting); }

保存代码。这里应该不会出现错误。现在,运行更新的 HelloWorldClient 类。控制台应该返回与之前一样的消息,表明服务工作正常。然而,在后台还发生了一些别的事。返回WTP,应该看到 TCP/IP Monitor 视图。单击它,更改 RequestResponse 窗格以显示 XML。

检查 Request 窗格的内容,它将客户端发送的信息表示成传出的 SOAP 请求消息。注意,它附加了一个安全头 ( wsse:Security),其中包含客户证书( dsig:KeyInfo 元素引用的 wsse:BinarySecurityToken)、实际的 SOAP 体 ( dsig:DigestValue) 的单向散列(也称报文摘要)和数字签名 ( dsig:Signature)。消息已经签名了。您会注意到一个消息的摘要 ( dsig:DigestValue),它是实际的 SOAP 体的加密的单向散列。

同样地,检查 Response 窗格。注意服务器返回的消息也附加了证书;这是服务器证书。为何要这样做? 当为服务类加批注时,您指定了 Sign.xml 策略,默认规定传入和传出的签名,因此客户端和服务器的签名进行了交换。

既然已经交换了签名,那么任一方都能够验证消息确实来自指定的发送方,并且没有被篡改(通过检查摘要)。我们已经实现了 消息完整性。 然而,请注意,消息的 soapenv:body 仍然是纯文本;没有消息的机密性(加密)。您能够清楚地看到 SOAP 请求中的 <name> 元素和 SOAP 响应中的 <return> 元素。

页面: 1, 2, 3

下一页 »