Securing Web Services with WebLogic Server 9.2
Pages: 1, 2, 3

Build a simple client for the HelloWorld service, and test it

We will now build a simple Java client to test the service we have just created. Even though the service can be tested using the WebLogic Server console tools, a client will eventually be needed to test the security facets.

Recall that for a client to successfully invoke a service, a client proxy for the service must first be generated. This can be done by using another Ant script. In the WORKSPACE_DIR/WSTest folder, create a new text file called gen-client.xml, and set its content to the following:

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



From the command shell that was open before, run the command ant -buildfile gen-client.xml. You should see a BUILD SUCCESSFUL message. This will generate the client proxy.

Back in WTP, refresh the WSTest project. The newly generated com.test.client package should be visible. WTP will report an error in the newly generated proxy, however. To fix it, add the webserviceclient.jar library to the project, in the same way that you added weblogic.jar to the project ( webserviceclient.jar can be found in the same folder as weblogic.jar). We can now begin coding our client.

In WTP, in the WSTest project, create a new java class called com.test.client.HelloWorldClient. Set its source to the following:

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

  }

}





This client is very simple. It merely makes use of the client proxy classes to obtain a reference to the service, and then invokes the sayHello method on it.

Run the code and an appropriate console print should appear.

The greeting returned was: Hello there, Gary

Note that even though we have activated the TCP/IP monitor, it is not used in this test. This is because the client is hitting the server directly on port 7001, instead of on the monitored port 7002. We will see how to change this later.

We have now built a very simple Web service and client but with no security mechanisms. That part comes next.

Generate a client key

For any form of message integrity (and encryption) to take place, a series of keys are required. Keys form the basis of certificates, and certificates are used to uniquely sign a message, which guarantees message integrity. A sender that sends a message can attach the sender's certificate to the message; the receiver would examine the certificate and could then verify the identity of the sender, as well as ensure that the message was not tampered with in transmission.

The client also needs to send a digital signature along with its certificate to add integrity to a message. The certificate by itself is meaningless. The client generates the digital signature by taking a hash of the message and encrypting it using a private key. In turn, the server uses the client's certificate (that is, public key), which is also attached to the message, to decrypt the hash, proving it came from the client. Next, it compares the decrypted hash to a hash it takes of the message. If the two hashes are equal, then it proves the message was not tampered with. In this tutorial, there are two communicating parties: WebLogic Server and the client. Both sides need keys.

WebLogic Server ships with its own demonstration "dummy" keys that are unusable for production but will suffice for demonstration purposes in this tutorial. We will use those dummy keys here, but bear in mind that a production environment should have those keys freshly generated. Creating the keys for both client and server is achieved by using the keytool command that you will use in a moment.

We will create client keys now. Specifically, we will create a key pair (public key and associated private key) for the client. The private key, from the key pair, will be used for decrypting and digitally signing messages. Key pairs are stored inside a keystore file. A keystore gets created as a side effect of creating a key pair. If the keystore specified already exists, the new key pair simply gets added to it. The public key is wrapped inside an X.509 certificate. The certificate and private key are stored in the keystore and identified by an alias.

Open a new command shell, and cd to the BEA JRE bin directory (typically something like C:\bea\jdk150_04\jre\bin). There, enter the following command:

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

(This command will create a keystore in the root directory. Feel free to change the location of this keystore to something more usable for your environment. If you are using a Unix-based operating system, change the file location to the more standard Unix naming file system convention instead of using "C:\".)

The name of the keystore created is C:\client_keystore.jks, and the password used to access the keystore is abc123. The alias used to refer to the key pair is client_key, and the password used to protect the private key is client_key_password. The algorithm used to create the key pair is RSA. The distinguished name, which is associated with the client_key alias, is CN=Client, OU=WEB AGE, C=US, and is used as the issuer and subject fields in the certificate. The key length is 1024 bits (the minimum size required by BEA). Finally, the corresponding certificate is valid for 1460 days (four years). If successful, a new file called client_keystore.jks should appear in C:\. This is the keystore that our client will use.

We should now verify that the keys were correctly created. In the command prompt window, type:

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

The command should show:

Alias name: client_key

Our keystore has been successfully created. The client keystore contains both a private key for the entity named client_key as well as a public key (also known as a certificate). These will be required for sending digital signatures as well as for encryption.

Do not close this second command shell window. We will need it again.

Import the client certificate into the server's trust

We must now tell the server to trust the client's certificate.

WebLogic Server maintains a trust file, which is essentially a list of the certificates that it will trust and accept. Unfortunately, the client key that we generated in the previous step is not a part of that trust file. This means that if WebLogic Server receives our client's certificate, it will outright reject it. What we need to do is import our client's certificate into the server's trust.

This is a two-stage process: we must first export the certificate from the client's keystore, and then import the certificate into the server's trustfile. The server is currently configured to use a "demo" trustfile ( Demotrust.jks), which in turn references the standard JDK trustfile typically located at:

C:\bea\jdk150_04\jre\lib\security\cacerts. (Note that this may differ for your operating system and installation settings.)

We need to import the client's certificate into this cacerts file.

First, we will export the client's certificate from client_keystore.jks. Enter the following command in the command shell window you opened to generate the key:

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

The next step is to import that certificate into the server's trust. Enter the following command:

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

(If your cacerts file is in a different location than C:\bea\JDK150~1\jre\lib\security\, substitute it in this command.)

When prompted, enter the password changeit. (This changeit is the default password that ships with WebLogic Server. Naturally, in production, you would change it to something more secure.)

You will then be prompted to Trust this certificate [no]:. Type y, and press Enter.

If all went well, you should see the message Certificate was added to keystore.

At this point, the client's certificate has been added to the server's trust. The server will now trust any client using the certificate.

Close the command shell window you were running these keytool commands from.

Add integrity to the service

Now that we have a key for the client, we can examine how it will assist us in achieving message integrity. If a party sends a SOAP message and attaches a certificate to the outgoing message, the receiver can confidently identify the sender by reading the certificate as well as ensure that the message has not been altered in transmission. This is achieved by having the client send its certificate along with the SOAP request. (Recall that the transmitted certificate will also have a hashed digest of the message. A tampered message would have a different hash, and the server can detect this. This is how to guarantee message integrity.)

To facilitate this, we have to make two changes:

  • We must edit the service class so that it requests a signature.
  • We must edit the client class so that it appends the signature to the request.

Getting the service to request message integrity is quite simple. We only need to add a single annotation to our source code. Switch back to WTP and open up HelloWorldService.java. Add the following highlighted code:

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;

        }



}

                      

All that we have done is added a single annotation ( @Policies/@Policy) and imported the appropriate packages required. This annotation will ensure that the generated Web service will always ask the client to sign its SOAP requests. Save your changes. There should be no errors.

We now need to regenerate the service. Using the same command shell you opened earlier to generate the service, run the command ant. You should see the message BUILD SUCCESSFUL; this should generate, package, and deploy the service.

Since the service has changed, we also need to regenerate the client proxy, based on the newly generated WSDL file. Go back to the browser, and reopen the URL for the WSDL file. Save it as WORKSPACE_DIR\WSTest\HelloWorldService.wsdl. Examine this file with a text editor. Notice there is now an element called wssp:Integrity, which contains quite a bit of information including, among other things, the contents of its trust. If you search through the wssp:TokenIssuer element body, you should see a reference to CN=Client, OU=WEB AGE, C=US, which is indeed the information that we generated in our client key and then imported into WebLogic Server's trust. Also note the inclusion of the new Sign.xml policy in the WSDL file:

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

...

</wsp:Policy> 

...

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

Near the bottom of the WSDL file, locate the line:

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

Change the port number to 7002, as follows:

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

Why do this? Simple. We want the client request to hit port 7002—our monitor port. Save and close the WSDL.

From the command shell window, run command ant -buildfile gen-client.xml. You should see the usual BUILD SUCCESSFUL message. The client proxy is now regenerated.

Back in WTP, refresh the WSTest project. Run the HelloWorldClient class again. What happens? An exception. Examine the stack trace that appears, and locate the message Failed to add Signature. This is telling us that the service requires a certificate, but our client is not attaching one. We need to change the client code so that it does indeed attach a certificate when invoking the service.

Open HelloWorldClient.java in WTP. Add the following import statements.

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;

Now, add the following highlighted code to the main method.

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

 }

                      

Save the code. There should be no errors. Now, run the updated HelloWorldClient class. The console should return the same message as before, indicating the service worked successfully. However, behind the scenes, something else has happened. Back in WTP, the TCP/IP Monitor view should have appeared. Click on it, and change both Request and Response panes to show XML.

Examine the contents of the Request pane, which represents what the client sent as an outgoing SOAP request message. Notice that it has appended a security header ( wsse:Security), which contains the client's certificate ( wsse:BinarySecurityToken, referenced by the dsig:KeyInfo element), the one-way hash (also known as message digest) of the actual SOAP body ( dsig:DigestValue), and the digital signature( dsig:Signature). The message has been signed. You'll notice that the message has a digest ( dsig:DigestValue), which is an encrypted one-way hash of the actual SOAP body.

Similarly, examine the Response pane. Notice that the message that the server returned also had a certificate attached to it; this is the server's certificate. Why did this happen? When you annotated the service class, you specified the policy Sign.xml, which by default specifies both inbound and outbound signatures—therefore both the client and server signatures were exchanged.

Now that signatures have been exchanged, either side can verify that the message was indeed from the specified sender and that no part of the message has been altered (by examining the digest). We have achieved message integrity. Notice, however, that the soapenv:body of the message is still in plain text; no message confidentiality (encryption) is taking place. You can clearly see the <name> element in the SOAP request and the <return> element in the SOAP response.

Pages: 1, 2, 3

Next Page »