Published on dev2dev (http://dev2dev.bea.com/)
http://dev2dev.bea.com/pub/a/2007/06/securing-web-services.html
See this if you're having trouble printing code examples
by Gary Ng and Matt Silver
06/05/2007
Web services are becoming more and more important in today's IT landscape, especially with the rapid growth and adoption of Service Oriented Architecture (SOA). By default, Web service invocations (requests and responses) are transmitted in plain text over a protocol such as HTTP. While this allows for a wider range of client interoperability, using plain text transmission incurs security risks. This article discusses some of these security risks and how to avoid them using BEA WebLogic Server's Web service security implementation.
This article is composed of two main parts. The first part is an overview of message integrity, message confidentiality, and authentication, and a brief discussion of how they can be enabled via policy annotations. The second part is a tutorial that takes the reader through an example of building a basic service, and then adding message integrity, message confidentiality, and authority in a step-by-step manner.
It is assumed that the reader is familiar with basic Web service development in BEA WebLogic Server 9.2 and basic security concepts such as digital signatures and encryption.
This section provides an overview of the many aspects of Web service security, including message integrity, confidentiality, and authentication. If you are familiar with this material, you can jump to the tutorial.
A basic Web service is composed of two components:
For the purposes of this article, we will assume that the service provider is a Java-based Web service deployed (and running) on a WebLogic Server 9 instance. The service consumer will be a Java client running in its own JVM, on a local or remote computer.
A Web service interaction begins by the client obtaining a WSDL file from the service provider. The WSDL file contains service and invocation information that the client can use to call the service. Once the client has the WSDL file, it can invoke a Web service by sending a plain-text SOAP request message (containing information such as the service to invoke and any parameters) to the provider. The provider receives the message, parses it, and then processes it. Once processing is complete, the provider returns a plain-text SOAP response message (containing any result data) to the client.
We can see from this that plain-text SOAP is the default transmission protocol. This protocol is, unfortunately, highly susceptible to all manner of security attacks. Enter WS-Security to the rescue.
WS-Security, which is defined and maintained by OASIS, outlines three key areas of security that need to be addressed by Web services. Specifically, when request and response messages are exchanged, the following need to be considered:
Message integrity ensures that a message came from a unique party, and that the message has not been tampered with in transmission. This is achieved by signing the outgoing SOAP message. Signing typically involves attaching a digital certificate (a chunk of encrypted plain text) that represents a unique hash of the message. When the receiving party receives the message, it can compare the contents of the message with the hash; if they match, then the message has not been tampered with in transmission.
Message confidentiality ensures that a message being transmitted can be understood only by the receiving party. This is achieved by encrypting the outgoing SOAP message.
Authentication ensures that an invocation of a Web service is allowed only for authenticated clients. This is achieved by having the client add a WS-Security token (for example, username/password, X.509 certification, Kerberos ticket) to the service invocation. This token is then authenticated by the server's security system at request time, and only if authentication is valid will the service proceed to process the request.
On its own, the default SOAP specification has none of these abilities. While it is possible to manually (programmatically) add support for these features, doing so would be time-consuming and error-prone. Fortunately, the WS-Security spec describes an implementation for these by leveraging the inherent extensibility of SOAP headers. With very little coding, a Web service message exchange can add integrity, confidentiality, and authentication.
WebLogic Server 9.2 fully supports the WS-Security standard. This means that by making some small changes to the way the Web service provider and client code are written, message integrity, confidentiality, and authentication can be enabled.
The WebLogic Server API makes it quite easy to enable security; the basic steps for enabling all three security facets are:
A keystore is essentially a collection of certificates that uniquely and securely identifies the server. Keystores can be created using the keystore command-line tool that ships with the JDK. A keystore can be imported into WebLogic Server using the WebLogic Server admnistration console.
The three facets can be used in any combination (for example, it is possible to just enable integrity, or to enable integrity as well as confidentiality, or all three). This article will demonstrate how to enable each facet.
Web service security is maintained in WebLogic Server 9.2 through the use of policy files. A policy file is an XML file that dictates how WebLogic Server will attempt to enable various security facets. A Web service is packaged with the policy files, and those policy files are embedded within the service's generated WSDL file at deployment time. When a client attempts to invoke the service, it should first obtain the service's WSDL file along with the policy information.
WebLogic Server security uses three policy files.
Sign.xml - Needed if message integrity is desired, this file contains configuration information to dicate how message signing takes place between client and server.Encrypt.xml - Needed if message confidentiality is desired, this file contains configuration information to dicate how encryption takes place between client and server.Auth.xml - Needed if authentication is desired, this file contains configuration information to decide how to authenticate a client and/or server.
Again, these policy files must be packaged with the Web service at deployment time. Naturally, only the policy file for the required security facet will be required (for example, if only message confidentiality is required, then only Encrypt.xml needs to be packaged; the other two can be ignored).
An issue of concern, then, is how to create these policy files. The documentation and schema for these policy files is available from BEA, and they can be written by hand using any text editor. The good news, however, is that these policy files can be automatically generated for you. Annotating the source code appropriately and running the jwsc Ant task will generate the required sign.xml/encrypt.xml/auth.xml files as needed. These autogenerated files will then be automatically packaged in with the deployable Web service.
For the most part, these autogenerated policy files will suffice. Their contents will reflect the annotations that were placed in the original source; as a developer securing a Web service, the only concern you have, then, is making sure you annotate the source code properly. There is no need for a developer to be concerned with creating the policy files.
Having said that, there may occasionally be a situation where the autogenerated policy files will not be usable. Some extra customization of the security framework may be required. An example of this would be a requirement to selectively encrypt only parts of a message instead of the entire body; by default, the autogenerated policy would encrypt the entire message. In this case, the developer is free to hand-create a policy file, and then annotate the source code to use that hand-created policy file. This will prevent autogeneration from taking place. This situation, however, is not usually required as the autogenerated policies are quite adequate (and require much less effort).
Recall that the development of a typical Web service involves coding a Java component (like a POJO or an EJB) and then annotating the code to produce a Java Web Service (JWS). To enable security for a service, the @Policies and the @Policy annotations are added to the JWS.
For example, you can add the following annotation to a JWS:
@Policies({
@Policy(uri="policy:Sign.xml"),
@Policy(uri="policy:Encrypt.xml"),
@Policy(uri="policy:Auth.xml")
})
Using these annotations signifies that the Web service requires all three facets: integrity, confidentiality, and authority. Naturally, each annotation has several optional attributes that can be added. We will examine these later in the article.
Now that we have covered the basics, let's jump into an example of how to create a Web service and add various security facets to it.
In this tutorial, we will create a simple "Hello World"-type service, and then add security facets to it. Our Web service container will be WebLogic Server, and our development environment will be the free, open-source WTP IDE. The entire process will be composed of the following basic steps:
Some of these steps (for example, configuring the WebLogic Server environment) are not the focus of this article and will not be discussed in great detail here; see related BEA documentation for more information on how to achieve these steps.
Install WebLogic Server 9.2 if it is not already installed. Use the BEA Configuration Wizard to create a new WebLogic Domain with admin username being weblogic and password being password. No special setup (for example, clusters, managed servers, JDBC, JMS) is required, so those stages can be skipped. Call the domain WSTestDomain and leave it in its default directory. This will create the domain directory. If you are using a Windows-based operating system, the domain directory will be something like C:\bea\user_projects\domains\WSTestDomain. We will refer to this directory as DOMAIN_DIR for the duration of this tutorial.
For the purposes of this tutorial, we will use the widely available and open-source Eclipse IDE, using the Web Tools Platform (WTP) plug-in. If you do not already have it, it can be downloaded from http://download.eclipse.org/webtools/downloads/. Note that this tutorial assumes that the reader is familiar with WTP/Eclipse. If the reader is not, we suggest the reader consult the Eclipse WTP help documentation.
The use of WTP here is arbitrary. Any IDE will suffice (or failing that, a command-line and text-editor-based environment, although a text-based environment will not allow you to examine message traffic—an extra tool facilitating this would be required). WTP was selected for this article simply because it is widely available and open-source (that is, free to download and use) and because it supports a TCP/IP monitor feature that will allow us to see the actual secured messages being transmitted.
Installing WTP typically involves obtaining a ZIP file containing the product and then unzipping it to your file system. When you have done this, launch it. When prompted, select a new workspace directory (for example, C:\workspaces). We will refer to this workspace directory as WORKSPACE_DIR for the duration of this tutorial.
Once WTP is started, you will need to change its default JVM to the JVM shipped with WebLogic Server. Switch to the Java perspective (unless it is already the active perspective). From the menu, select Window | Preferences. In the left pane expand Java and select Installed JREs underneath it. We need to add the BEA supplied JDK. In the right pane, click Add.... Click the Browse... button, navigate to your BEA JDK home directory (typically C:\bea\jdk150_04), and click OK. Both JREs should now be listed. Check the box for the newly added JRE (jdk150_04), and click OK. If a prompt appears asking you to rebuild, then click Yes.
WTP has now been configured, and we can go on to activate the monitor.
To test that security is working as desired, we will activate the TCP/IP Monitor feature of WTP. This will allow us to intercept and examine messages being exchanged between client and service. To do that, we have to define a server instance pointing to our WebLogic Server from inside WTP. First of all, stop any currently running WebLogic Server instance (using stopWebLogic.cmd script).
In WTP, open the Servers view. Right-click anywhere on it, and select New | Server. In the pop-up that appears, expand BEA Systems, and select Generic BEA WebLogic Server v9.2. Click Next. In the JRE drop-down menu, select jdk150_04, and then click Next.
Change the Domain Directory to point at DOMAIN_DIR. Change the Start Script and Stop Script fields to point at the corresponding start/stop scripts in DOMAIN_DIR/bin (startWebLogic.cmd script and stopWebLogic.cmd). Change the Password to password, and then click Finish.
Back in the servers view, right-click on the newly listed Generic BEA WebLogic Server v9.2, and select Monitoring | Properties. In the pop-up Monitoring Ports window that appears, click Add.... In the pop-up menu that appears after that, simply click OK. You should now see a new monitor port (on 7001/7002) listed. Select it and click Start. Click OK.
At this point, any traffic sent to port 7002 will be passed to the monitor, which will display it for us before forwarding it to the (correct) server port 7001. This will let us examine incoming and outgoing traffic.
We will now create a basic Web service JWS. It will be a very simple service. Simply passed a String, it will return an appropriate greeting. We will then build and deploy the service.
In WTP, create a new Java project called WSTest. In that project, create a new Java class called com.test.HelloWorldService.
We now need to add the BEA weblogic.jar library to the project so our code will compile properly. Right-click on the WSTest project, and select Properties. The Properties window appears. Click Java Build Path in the left pane. Click Libraries in the right pane. Click Add External JARs. In the JAR Selection window that appears, navigate to your WebLogic Server library folder and select weblogic.jar (typically something like C:\bea\weblogic92\server\lib\weblogic.jar). Click Open. You should now see weblogic.jar listed as one of the Libraries. Click OK.
Set the source of HelloWorldService.java to the following:
package com.test;
import javax.jws.*;
@WebService(name = "HelloWorldPortType",
serviceName = "HelloWorldService",
targetNamespace = "http://mycompany.com")
public class HelloWorldService {
public String sayHello(String name) {
return "Hello there, " + name;
}
}
Note the use of the @WebService annotation, which defines this class as a Web service. Also, note the one business method, sayHello, which takes as an argument a single String, and returns an appropriate greeting.
The next step is to create an Ant script that will build and then deploy the service. Create a text file in the project directory (WORKSPACE_DIR/WSTest) called build.xml, and set its content to the following:
<project default="build">
<taskdef name="jwsc"
classname="weblogic.wsee.tools.anttasks.JwscTask" />
<target name="jwsc">
<jwsc
srcdir="."
destdir="."
>
<jws file="com/test/HelloWorldService.java" />
</jwsc>
</target>
<target name="build" depends="jwsc">
<wldeploy action="deploy"
name="HelloWorldService"
source="com/test/HelloWorldService.war"
user="weblogic"
password="password"
adminurl="t3://localhost:7001/"
targets="AdminServer"/>
</target>
</project>
Nothing very interesting is going on here. Two targets are defined—one that builds the actual service, and one that deploys it.
We can now run the service. Start the admin server in the WSTestDomain. (This can be done from the Servers view inside WTP by right-clicking on Generic BEA WebLogic Server v9.2, and then clicking Start.)
Open a command shell window and execute the setDomainEnv script located in DOMAIN_DIR/bin. Then, in the same command shell window, cd to WORKSPACE_DIR/WSTest. From there, run the command ant.
The script should run, and you should see a BUILD SUCCESSFUL message. Do not close the command shell as it will be needed again. At this point, the service should be built, deployed, and running. Open a browser and browse to http://localhost:7001/HelloWorldService/HelloWorldService?WSDL. The WSDL file for the service should be visible. Bookmark this page, as we will need it throughout this tutorial to generate the client proxy. For now, use the browser to save this page as HelloWorldService.wsdl in the WORKSPACE_DIR/WSTest directory (make sure that a ".xml" extension is not added to the end of the WSDL filename).
|
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.
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.
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.
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:
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.
|
We will now look at adding message confidentiality. This means we would like our message (SOAP body) encrypted between client and server. The basic procedure is the same as it was for adding message integrity: annotate the service and regenerate, and then update the client.
Open HelloWorldService.java in WTP. Add the following highlighted annotation:
@Policies({
@Policy(uri="policy:Sign.xml"),
@Policy(uri="policy:Encrypt.xml")
})
(Do not forget to add the comma after the Sign.xml annotation.)
This annotation specifies that we would like the client and server to use encryption. By default, encryption is two-way (that is from client to server and server back to client). If only one-way encryption was required, we could have added an optional direction attribute to the annotation to handle this.
We now need to rebuild the service. From the command shell window, run the ant command. As before, you should see a BUILD SUCCESSFUL message.
Now, to update the client. First, obtain and save the WSDL file from the browser as you did in the previous section. Open it with an editor. You should notice a new element: wssp:Confidentiality. This is a hint to the client that encryption should be used. Also note the inclusion of the Encypt.xml policy, as evidenced by the following:
<wsp:Policy s0:Id="Encrypt.xml">
...
</wsp:Policy>
...
<portType name="HelloWorldPortType" wsp:PolicyURIs="#Sign.xml #Encrypt.xml">
..
</portType>
Since the WSDL file was regenerated by the new deployment, we need to update the port number for the monitor again. 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"/>
Save and close the WSDL file. Now, in the command shell, run command ant -buildfile gen-client.xml. You should see a BUILD SUCCESSFUL message. The client proxy has been updated.
What next? Well, there is actually no need for any other changes. The client does not need any code added to it to cause the messages to be encrypted. All the encryption work will be provided by the client proxy. This is a major advantage to using the Web services security mechanisms; not a single line of encryption code has to be written by any developers!
Run the HelloWorldClient. You should see the usual greeting response. What changed? At the outset, nothing much. However, examine the TCP/IP monitor. You should see that both the request and the response have been encrypted; you should not be able to locate the <name> element that you could earlier. In its place is a <CipherData> element, along with some encrypted text. Note that only the message body (the name element for the request and the return element for the response) have been encrypted; all the message headers have been left unencrypted. Also, note that a ns1:EncryptedKey element has been added to the security header (wsse:Security).
Message confidentiality has been added to the service!
Next we can add an authorization mechanism to the Web service. Simply put, we can force the client to supply a username and password with the SOAP request; if that username and password does not exist inside WebLogic Server's user registry, the server will deny access to the service. This allows a form of access control. Note that this is different from the identity portion of message integrity. Message integrity asserts identity of a client via a certificate; authority concerns itself with the client having the actual authority to invoke the service. If a client attempts to invoke a service without successfully authenticating against the WebLogic Server user registry, the server will deny access to the service.
In this part of the tutorial, we will add a user to the WebLogic Server user registry, and then change the service to force the client to authenticate. We will then test this using the client.
The first step is to create a user in the WebLogic Server user registry. By default, the user registry is an embedded LDAP server that ships with WebLogic Server, and users can be added to it via the WebLogic Server administration console. Make sure the server is running, and use a browser to browse to http://localhost:7001/console. Log in with username weblogic and password password. The administration console will open.
In the Domain Structure pane on the left, click Security Realms. In the right pane, click myrealm. This myrealm represents WebLogic Server's current security configuration. We will add a test user to this configuration. Click the Users and Groups tab. Click the New button.
For the Name, enter suzie. For the Description, enter An authenticated Web service user. For the Password and Confirm Password fields, enter munchkin. Click OK.
We have created a new user in WebLogic Server's user registry. We can now edit the service to require that all clients authenticate themselves by presenting a username/password (which should hopefully match a user in the registry). In WTP, open HelloWorldService.java and add the following highlighted annotation.
@Policies({
@Policy(uri="policy:Sign.xml"),
@Policy(uri="policy:Encrypt.xml"),
@Policy(uri="policy:Auth.xml"
,direction=Policy.Direction.inbound)
})
The new annotation specifies that an authorization policy should be enforced but only for inbound traffic. (It would not make much sense for the server to have to authenticate back to the client in this example.)
Save the file. There should be no errors. We can now build and deploy the service. From the command shell window, run the ant command. As before, you should see a BUILD SUCCESSFUL message.
Now that the service has been regenerated, we also have to regenerate the client proxy as we did before. Obtain and save the WSDL file from the browser as you did in the previous section. Open it with an editor. A new element wssp:Identity should appear. This is a hint to the client that a username and password should be sent when the service is invoked. Also note the inclusion of the new Auth.xml policy, as evidenced by the following:
<wsp:Policy s0:Id="Auth.xml">
...
</wsp:Policy>
...
<input>
<s2:body parts="parameters" use="literal"/>
<wsp:Policy>
<wsp:PolicyReference URI="#Auth.xml"/>
</wsp:Policy>
</input>
As before, since the WSDL file was regenerated by the new deployment, we need to update the port number for the monitor. 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"/>
Save and close the WSDL file. Now, in the command shell, run the command ant -buildfile gen-client.xml. You should see a BUILD SUCCESSFUL message. The client proxy has been updated.
Back in WTP, refresh the WSTest project. Run the HelloWorldClient class. What happens?
javax.xml.rpc.soap.SOAPFaultException: Unable to add security token for identity
Our client is attempting to invoke the service but is not providing any authorization credentials. We need to change the client code so that it invokes the service with that suzie/munchkin username/password that we created earlier. Let's do this now; add the following highlighted code to the main method in HelloWorldClient.java:
stub._setProperty(
WSSecurityContext.CREDENTIAL_PROVIDER_LIST,
credProviders);
String username = "suzie";
String password = "munchkin";
cp = new ClientUNTCredentialProvider(username.getBytes(),
password.getBytes());
credProviders.add(cp);
stub._setProperty(
Run the code. The usual greeting message should now be returned. We now know that the client has been forced to add credentials before it could invoke the service. Check the monitor and examine the request. There should be a new section:
<wsse:UsernameToken> <wsse:Username>suzie</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/ oasis-200401-wss-username-token-profile-1.0#PasswordText">munchkin</wsse:Password> </wsse:UsernameToken>
These are indeed the credentials that we have supplied. Note that these credentials are being transmitted in plain text. The WS-Security spec does not talk about encrypting user credentials; it is recommended that transport layer security (that is, securing the entire TCP/IP socket) be used here.
We have successfully added authentication to our Web service!
WebLogic Server 10 was recently released. What does this mean to your existing secured Web services? Fortunately, not much. Services deploy and run unchanged in version 10. Client code will, however, need to be regenerated using the version 10 client API. Additionally, WebLogic Server 10 has added support for WS-SecurityPolicy 1.2, and now supports WS-Security 1.1. (WebLogic Server 9.2 supports WS-Security 1.0.)
The significance of the inclusion of the WS-SecurityPolicy 1.2 specification is that you can now create "standardized" security policy files versus BEA proprietary ones. WebLogic Server 9.2, which was released before the WS-SecurityPolicy specification had been released, uses security policy files written under an old version of the WS-Policy specification using a proprietary BEA schema for security policy.
Which type of policy files should you use? BEA recommends you use the WS-SecurityPolicy standard policy files in place of their proprietary policy files. Actually, both types of policy files are supported in version 10, but they are incompatible, so only one type can be used within a given Web service.
However, before you rush out and migrate your WebLogic Server 9.2 policy files to the new standard, keep in mind WS-SecurityPolicy has some limitations, since it was available only in draft form (and still is) when BEA implemented it. Specifically, if you require element-level digital signatures and/or encryption (that is, you don't want to digitally sign and/or encrypt the entire body of the message) or you require SAML 1.1, then you still have to use the proprietary, BEA WebLogic 9.2-style policies.
In terms of WS-Security, even though WebLogic Server 10 supports WS-Security 1.1, BEA still recommends you use WS-Security 1.0 policies for interoperability. You should use WS-Security 1.1 policies only if you have stronger security requirements. Furthermore, WS-Security 1.1 is supported only by using the new, WS-SecurityPolicy 1.2 policy files.
In this article, we have discussed the three facets of Web services security: message integrity, message confidentiality, and authentication. We have seen that coding a "secure" Web service with these assets is quite simple; merely annotating the Web service code with the appropriate annotations is all that is required. We saw what annotations to use to enable each of the facets. We looked at how the client code had to change to invoke the secured service, and saw how the SOAP messages themselves changed to reflect the newly added security. In the tutorial we built a Web service that incorporated all three facets added to it in an incremental fashion.
Due to the "plain text" nature of SOAP messaging, it is important to have robust security capabilities when deploying Web services as a part of a production enterprise environment. WS-Security combined with WebLogic Server offers an excellent solution to this problem, allowing developers to easily deploy and maintain secure enterprise services.
Gary Ng is a senior consultant with WebAge Solutions and has been involved in various J2EE projects since 2001.
Matt Silver is a technology consultant and trainer, currently serving as a senior consultant for Web Age Solutions.
Return to Dev2Dev.