Message-level security with JAX-WS on WebSphere Application Server V7, Part 3: Programmatic client control using Web Services Security APIs

In Part 1, you learned how to provide message-level security using JAX-WS on WebSphere® Application Server V7. In Part 2, you used the UsernameToken for JEE programmatic authorization decisions. In Part 3, you'll learn how to develop a JAX-WS client that can consume a secure service running on WebSphere Application Server V7 using the Web Services Security (WSS) APIs.

Share:

Hyen-Vui (Henry) Chung, Senior Web Services Architect, IBM

Henry Chung photoHenry Chung is an architect on the WebSphere Web Services development team. Prior to this role, Henry was the architect and lead developer of Web services security on the WebSphere platform. Henry has been in middleware development for over eight years and has developed many security features for the WebSphere platform. His current focus is leading the development of the latest WebSphere Web services specifications. He also helps customers and other IBM teams apply Web services solutions. His primary goal is to deliver WebSphere Web services technology to meet real-world needs.



Bill Griffith (wgriffith@us.ibm.com), Senior IT Architect, IBM

Bill Griffith photoBill Griffith is a Senior IT Architect in the Worldwide WebSphere Business Partner organization. In this role, Bill works with IBM business partners to architect and design business partner solutions that leverage IBM middleware as a way to decrease time-to-market and decrease costs.


developerWorks Contributing author
        level

31 August 2010

Also available in Russian

In Part 1, you learned how to build JAX-WS web services that use message-level security by using policy sets that give you a declarative way of specifying how message-level security should be performed. That method works well in most cases and is the preferred way to specify message-level security. However, sometimes the consumers of the service might need more dynamic control in order to programmatically change the UsernameToken value or to dynamically specify which public key to use. The WSS APIs give you this programmatic control over how the consumer builds a SOAP message that adheres to the service provider's WS-Security policy set. Additionally, the WSS APIs allow Spring clients to invoke JAX-WS service providers on WebSphere Application Server V7 using WS-Security.

Create a JAX-WS web service

Let's start with a plain old Java object (POJO) annotated with a web service annotation to define that POJO as a service provider. Simply create a new Dynamic Web Project with a new Java class and copy the code from Listing 1 into that class.

Listing 1. Server-side code
package com.ibm.dwexample;
import javax.jws.WebService;

@WebService
public class HelloWorldProvider {
  public String sayHello(String msg) {
    System.out.println("[helloworld service] received " + msg);
    return "Hello " + msg;
  }
}

After you have saved the code, you should see something like Figure 1 with the HelloWorldProvider class created.

Figure 1. Service provider dynamic web project
Service provider dynamic web project

That's it! A simple POJO with the @WebService annotation is all that is necessary to create a JAX-WS web service.


Secure the service provider

In this article, we use WS-Security policy sets to require that SOAP messages are encrypted and signed. Rational Application Developer V7.5 comes with a set of default policy sets; the examples in this article use the Username WS-Security default policy set. To secure the server provider, do the following:

  1. Right-click the service provider and select Manage Policy Set Attachment as shown in Figure 2.
    Figure 2. Attach policy set to service provider
    Attach policy set to service provider
  2. In the End Point Definition dialog, shown in Figure 3, verify that HelloWorldProviderService is the selected service and select <all endpoints> as the list of endpoints that will attach to the policy set. Select Username WSSecurity default and Provider sample as the desired policy set and policy set binding. Click OK, then Finish.
    Figure 3. Set Username WSSecurity default policy set
    Set Username WSSecurity default policy set
  3. Deploy the project to WebSphere Application Server V7.
  4. Verify that the policy set and bindings were successfully attached to the service provider by opening the administrative console of your WebSphere Application Server V7 runtime, as shown in Figure 4.
    Figure 4. Run administrative console
    Run administrative console
  5. Select Services => Service providers => HelloWorldProviderService, as shown in Figure 5. Notice that the Username WSSecurity default policy set is attached along with the Provider sample bindings.
    Figure 5. Verify policy set and bindings
    Verify policy set and bindings
  6. You've now configured the service provider to require WS-Security. The Username WSSecurity default policy set uses the Username Token for authentication, so you also need to enable security in the administrative console. To do this, select Security => Global security and verify that both Enable administrative security and Enable application security are selected, as shown in Figure 6.

    Tip: You need to restart WebSphere Application Server if you had to enable security.

    Figure 6. Enable application security
    In the adminstrative console, navigate to Security and then Global security to verify that both Enable administrative security and Enable application security are selected.

    (See a larger version of Figure 6.)

Next we'll look at how to configure the client consumer to invoke this service provider with WS-Security using the WSS APIs.


Generate a JAX-WS consumer

Now that you have the service provider running on WebSphere Application Server V7 with the WS-Security default policy set and sample bindings, you can create a JAX-WS consumer that invokes this service provider by doing the following:

  1. From Rational Application Developer, create a new Java project with the name HelloWorldConsumer.
  2. Select the service provider from which Rational Application Developer will generate a client proxy as shown in Figure 7.
    Figure 7. Generate JAX-WS client proxy
    Generate JAX-WS client proxy
  3. From the Web Service Client wizard, ensure that IBM WebSphere JAX-WS is the chosen web service runtime then click the Client project: link.
  4. Select HelloWorldConsumer as the client project.
  5. Accept the defaults and click the Finish button, which results in Rational Application Developer generating a JAX-WS client proxy class and supporting classes.

Provide client credentials programmatically

Now let's create a Java class that uses the generated JAX-WS proxy to invoke the JAX-WS service provider. Because the service provider is protected according to the specifications of the Username WS-Security default policy set, you need to use the WSS APIs to build a SOAP message programmatically that adheres to the service provider's policy set.

From the HelloWorldConsumer project, create a new Java class called ClientTest and replace the generated code with the code from Listing 2. For your convenience, a project export of all the code in this article is included in the Download section.

Listing 2. ClientTest.java
package com.ibm.dwexample.client;

import java.io.FileInputStream;
import java.security.cert.CertStore;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.xml.ws.BindingProvider;
import com.ibm.dwexample.HelloWorldProvider;
import com.ibm.dwexample.HelloWorldProviderService;
import com.ibm.websphere.wssecurity.callbackhandler.UNTGenerateCallbackHandler;
import com.ibm.websphere.wssecurity.callbackhandler.X509ConsumeCallbackHandler;
import com.ibm.websphere.wssecurity.callbackhandler.X509GenerateCallbackHandler;
import com.ibm.websphere.wssecurity.wssapi.WSSConsumingContext;
import com.ibm.websphere.wssecurity.wssapi.WSSFactory;
import com.ibm.websphere.wssecurity.wssapi.WSSGenerationContext;
import com.ibm.websphere.wssecurity.wssapi.decryption.WSSDecryption;
import com.ibm.websphere.wssecurity.wssapi.encryption.WSSEncryption;
import com.ibm.websphere.wssecurity.wssapi.signature.WSSSignature;
import com.ibm.websphere.wssecurity.wssapi.token.SecurityToken;
import com.ibm.websphere.wssecurity.wssapi.token.UsernameToken;
import com.ibm.websphere.wssecurity.wssapi.token.X509Token;
import com.ibm.websphere.wssecurity.wssapi.verification.WSSVerification;

public class ClientTest {

public static void main(String[] args) {

final String KEY_PATH = "C:/Program Files/IBM/SDP/runtimes/base_v7/profiles
  /was70profile2/etc/ws-security/samples/";
		
try {
  // JAX-WS proxy client generated from RAD
  HelloWorldProviderService srv = new HelloWorldProviderService();
  HelloWorldProvider port = srv.getHelloWorldProviderPort();
			
  // use BindingProvider to hook into message context
  BindingProvider bp = (BindingProvider) port;
  Map<String,Object> requestContext = bp.getRequestContext();
		
  WSSFactory wssfactory = WSSFactory.getInstance();
  WSSGenerationContext generationContext = wssfactory.newWSSGenerationContext();
			
  // Attach the username token to the message 
  UNTGenerateCallbackHandler untCallback = new 
    UNTGenerateCallbackHandler("admin", "admin",true,true);
  SecurityToken unt = wssfactory.newSecurityToken(UsernameToken.class,
    untCallback);
  generationContext.add(unt);
			
  // specify key for signing to match the configuration of the ‘provider sample’ 
  // that is attached to service provider
  X509GenerateCallbackHandler signerCallback = new
    X509GenerateCallbackHandler("", 
    KEY_PATH + "dsig-sender.ks", 
    "jks", "client".toCharArray(),"soaprequester", "client".toCharArray(),
    "CN=SOAPRequester, OU=TRL, O=IBM, ST=Kanagawa, C=JP", null);
  SecurityToken signerToken=wssfactory.newSecurityToken(X509Token.class,
    signerCallback);
  WSSSignature signature = wssfactory.newWSSSignature(signerToken);
			
  // specify what to sign
  signature.addSignPart(WSSSignature.BODY);
  signature.addSignPart(WSSSignature.ADDRESSING_HEADERS);
  signature.addSignPart(WSSSignature.TIMESTAMP);				
  signature.addSignPart(unt);
  generationContext.add(signature);
		
  // specify key to use for encryption (same key service provider expecting)
  X509GenerateCallbackHandler encryptionCallback = new
    X509GenerateCallbackHandler("",
    KEY_PATH + "enc-sender.jceks", 
    "jceks", "storepass".toCharArray(),"bob", null, "CN=Bob, O=IBM, C=US",null);
  SecurityToken encryptingToken = wssfactory.newSecurityToken(X509Token.class, 
    encryptionCallback);
  WSSEncryption encryption = wssfactory.newWSSEncryption(encryptingToken);
  encryption.setKeyEncryptionMethod(WSSEncryption.KW_RSA15);
			
  // specify what to encrypt
  encryption.addEncryptPart(unt, false);
  encryption.addEncryptPart(WSSEncryption.BODY_CONTENT);
  encryption.addEncryptPart(WSSEncryption.SIGNATURE);
  generationContext.add(encryption);

  // encode message according to generationContext setting
  generationContext.process(requestContext);
			
  // specify key to be used for decryption
  WSSConsumingContext consumerContext = wssfactory.newWSSConsumingContext();
  X509ConsumeCallbackHandler decryptionCallback = new
    X509ConsumeCallbackHandler(
    "", KEY_PATH + "enc-receiver.jceks", "jceks", "storepass".toCharArray(),
    "bob", "keypass".toCharArray(), "CN=Bob, O=IBM, C=US");
  WSSDecryption decryption = wssfactory.newWSSDecryption(X509Token.class, 
    decryptionCallback);
  decryption.addAllowedKeyEncryptionMethod(WSSEncryption.KW_RSA15);
  decryption.addRequiredDecryptPart(WSSDecryption.BODY_CONTENT);
  decryption.addRequiredDecryptPart(WSSDecryption.SIGNATURE);
  consumerContext.add(decryption);
				
  // Use intermediate certificate authority to verify certificate
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  X509Certificate cacert = (X509Certificate) cf.generateCertificate(new 
    FileInputStream(KEY_PATH + "intca2.cer"));
  Set<Object> eeCerts = new HashSet<Object>();
  eeCerts.add(cacert);
  java.util.List<CertStore> certList = new
    java.util.ArrayList<CertStore>();
  CollectionCertStoreParameters certparam = new
    CollectionCertStoreParameters(eeCerts);
  CertStore cert = CertStore.getInstance("Collection", certparam,
    "IBMCertPath");
  certList.add(cert);
			
  // specify signature verification key
  X509ConsumeCallbackHandler verificationHandler = new
    X509ConsumeCallbackHandler(
    KEY_PATH + "dsig-receiver.ks", "jks", "server".toCharArray(),
    certList, java.security.Security.getProvider("IBMCertPath"));
  WSSVerification verification = 
    wssfactory.newWSSVerification(X509Token.class,verificationHandler);
  consumerContext.add(verification);
			
  // decode message according to consumerContext settings
  consumerContext.process(requestContext);
		
  String resp = port.sayHello("Griffith");
  System.out.println("[response] " + resp);
	
} catch(Exception e) {
  e.printStackTrace();
}
}
}

Be sure to correct the KEY_PATH value as required on your system.

The code sample in Listing 2 is configured to match the provider sample binding that you attached to the service provider. If you examine the binding's configuration in the WebSphere Application Server administrative console, you'see settings for the keys used for encryption, decryption, and signing. In a production setting, you should customize the keys as demonstrated in Part 1 of this article series.


Test and verify

Now that you have secured the service provider and developed a service consumer that uses the WSS APIs to adhere to the policy set requirements of the service provider, you can verify that the code works.

  1. From Rational Application Developer, right-click ClientTest.java and select Run As => Run Configurations. You'll see the Run Configurations dialog, as shown in Figure 8.
    Figure 8. Set JAAS arguments for ClientTest
    Set JAAS arguments for ClientTest
  2. The consumer needs to use Java Authentication and Authorization Service (JAAS) to pass in the username credentials, so you need to specify the following VM argument to point to the JAAS login configuration file; for example, by default on the Windows platform:
    -Djava.security.auth.login.config=”C:\Program 
    Files\IBM\SDP\runtimes\base_v7\profiles\was70profile1\properties
    \wsjaas_client.conf”

    If you examine the wsjaas_client.conf file, you'll see that it only specifies how to find the classes used by JAAS and does not contain any user credentials or WS-Security settings; that is all done programmatically through the WSS APIs shown in Listing 2.
  3. Now click Run to generate client results, as shown in Figure 9, and server-side results, as shown in Figure 10.
    Figure 9. JAX-WS consumer results
    JAX-WS consumer results..
    Figure 10. JAX-WS provider results
    JAX-WS provider results.
  4. You can also use the TCP/IP monitor (see Part 1 for how to configure) to see the SOAP request and response messages, as shown in Figure 11.
    Figure 11. SOAP request and response seen through TCP/IP monitor
    Use the TCP/IP monitor to see the SOAP request and response messages.

    (See a larger version of Figure 11.)


Add Spring to the mix

Many clients use the Spring framework to create web service clients, or consumers. The Spring remoting capability provides that ability through a configuration such as the one shown in Listing 3. Spring then essentially creates a dynamic proxy on the fly by which it invokes a web service as defined by the Web Service Definition Language (WSDL) in the configuration. However, because this process is done dynamically at runtime, the "attach policy set" to the client proxy capability demonstrated in Part 1 doesn't have a client proxy to select in Rational Application Developer that can attach to the policy set. Therefore, you can use the WSS APIs to enable Spring clients to connect to a WebSphere Application Server V7 web service protected by WS-Security policy sets.

To do this, you can use JAX-WS handlers to intercept the dynamic Spring proxy before it sends a SOAP request to the provider and modify the request as required to comply with the service provider's policy set. As shown in Listing 3, the Spring configuration contains a handlerResolver property that specifies a callback class. Also note in Listing 3 that the serviceInterface used is the one generated by Rational Application Developer V7.5.

Listing 3. Spring configuration (config/clientSpring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns=http://www.springframework.org/schema/beans
  xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
  xsi:schemaLocation="http://www.springframework.org/schema/beans   
  http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="myHandler" class="com.ibm.dwexample.client.MyHandlerResolver"/>
<bean id="helloProxy"   
      class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
  <property name="serviceInterface" 
            value="com.ibm.dwexample.HelloWorldProvider"/>
  <property name="wsdlDocumentUrl" 
      value="http://localhost:9080/HelloWorldProject/HelloWorldProviderService?WSDL" />
  <property name="namespaceUri" value="http://dwexample.ibm.com/" />
  <property name="serviceName" value="HelloWorldProviderService" />
  <property name="portName" value="HelloWorldProviderPort" />
  <property name="handlerResolver" ref="myHandler"/>
</bean>
</beans>

The custom handler simply provides a list of handlers to invoke as shown in Listing 4.

Listing 4. Client-side custom handler list
package com.ibm.dwexample.client;
public class MyHandlerResolver implements HandlerResolver {

  public java.util.List<Handler> getHandlerChain(PortInfo portInfo) {
    List<Handler> handlerChain = new ArrayList<Handler>();
    CredsHandler credsHandler = new CredsHandler();
    handlerChain.add(credsHandler);
    return handlerChain;
  }
}

Since only one handler is being used, Listing 4 shows the handler that uses the WSS APIs to adhere to the Username WSSecurity default policy set attached to the service provider. The code has comments to better explain each part of the Java code. Note that this code is essentially the code in Listing 2, but packaged in a SOAPHandler fashion.

Listing 5. CredsHandler Java class
package com.ibm.dwexample.client; 
public class CredsHandler implements SOAPHandler<SOAPMessageContext> {
// TODO:  must point to keys on your system
final String KEY_PATH = "C:/Program 
Files/IBM/SDP/runtimes/base_v7/profiles/was70profile2/etc/ws-security/samples/";

public void close(MessageContext messagecontext) {}
public Set<QName> getHeaders() {return null;}
public boolean handleFault(SOAPMessageContext messagecontext){return true;}
public boolean handleMessage(SOAPMessageContext messagecontext) {
  Boolean outbound = (Boolean)  
    messagecontext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
  // only concerned with outbound
if (outbound) {
  System.out.println("[CredsHandler] " + messagecontext);
 try {
  WSSFactory wssfactory = WSSFactory.getInstance();
  WSSGenerationContext generationContext = wssfactory.newWSSGenerationContext();
  // Attach the username token to the message 
  UNTGenerateCallbackHandler untCallback = new UNTGenerateCallbackHandler("admin", 
    "admin",true,true);
  SecurityToken unt = wssfactory.newSecurityToken(UsernameToken.class, untCallback);
  generationContext.add(unt);
  // specify key for signing to match the configuration of the 'provider sample'
  // that is attached to the service provider  
  X509GenerateCallbackHandler signerCallback = new X509GenerateCallbackHandler("", 
    KEY_PATH + "dsig-sender.ks", 
    "jks", "client".toCharArray(),"soaprequester", "client".toCharArray(),
    "CN=SOAPRequester, OU=TRL, O=IBM, ST=Kanagawa, C=JP", null);
  SecurityToken signerToken = wssfactory.newSecurityToken(X509Token.class, 
    signerCallback);
  WSSSignature signature = wssfactory.newWSSSignature(signerToken);
  // specify what to sign
  signature.addSignPart(WSSSignature.BODY);
  signature.addSignPart(WSSSignature.ADDRESSING_HEADERS);
  signature.addSignPart(WSSSignature.TIMESTAMP);
  signature.addSignPart(unt);
  generationContext.add(signature);
  // specify key to use for encryption (same key service provider expecting)
  X509GenerateCallbackHandler encryptionCallback = new 
    X509GenerateCallbackHandler("",
    KEY_PATH + "enc-sender.jceks", 
    "jceks", "storepass".toCharArray(),"bob", null, "CN=Bob, O=IBM, C=US",null);
  SecurityToken encryptingToken = wssfactory.newSecurityToken(X509Token.class, 
   encryptionCallback);
  WSSEncryption encryption = wssfactory.newWSSEncryption(encryptingToken);
  encryption.setKeyEncryptionMethod(WSSEncryption.KW_RSA15);
  // specify what to encrypt
  encryption.addEncryptPart(unt, false);
  encryption.addEncryptPart(WSSEncryption.BODY_CONTENT);
  encryption.addEncryptPart(WSSEncryption.SIGNATURE);
  generationContext.add(encryption);
  // encode message according to generationContext settings
  generationContext.process(messagecontext);
  // specify key to be used for decryption
  WSSConsumingContext consumerContext = wssfactory.newWSSConsumingContext();
  X509ConsumeCallbackHandler decryptionCallback = new X509ConsumeCallbackHandler(
    "", KEY_PATH + "enc-receiver.jceks", "jceks", "storepass".toCharArray(),
    "bob", "keypass".toCharArray(), "CN=Bob, O=IBM, C=US");
  WSSDecryption decryption = wssfactory.newWSSDecryption(X509Token.class, 
    decryptionCallback);
  decryption.addAllowedKeyEncryptionMethod(WSSEncryption.KW_RSA15);
  decryption.addRequiredDecryptPart(WSSDecryption.BODY_CONTENT);
  decryption.addRequiredDecryptPart(WSSDecryption.SIGNATURE);
  consumerContext.add(decryption);
  // use intermediate certificate authority to verify certificate
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  X509Certificate cacert = (X509Certificate) cf.generateCertificate(new 
    FileInputStream(KEY_PATH + "intca2.cer"));
  Set<Object> eeCerts = new HashSet<Object>();
  eeCerts.add(cacert);
  java.util.List<CertStore> certList = new java.util.ArrayList<CertStore>();
  CollectionCertStoreParameters certparam = new 
    CollectionCertStoreParameters(eeCerts);
  CertStore cert = CertStore.getInstance("Collection", certparam, "IBMCertPath");
  certList.add(cert);
  // specify signature verification key
  X509ConsumeCallbackHandler verificationHandler = new X509ConsumeCallbackHandler(
    KEY_PATH + "dsig-receiver.ks", "jks", "server".toCharArray(),
    certList, java.security.Security.getProvider("IBMCertPath"));
  WSSVerification verification = 
    wssfactory.newWSSVerification(X509Token.class,verificationHandler);
  consumerContext.add(verification);
  // decode message according to consumerContext settings
  consumerContext.process(messagecontext);
} catch (Exception e) {
  e.printStackTrace();
}
}
return true;
}}

The handler simply intercepts the outbound message and adds a UNT, then signs and encrypts the various parts of the SOAP message as required by the service provider. The keys used match the sample keys that were attached to the service provider, but you can refer to Part 1 for information on how to customize the keys.

Once the handler is in place, you can create a Spring client that pulls in the dynamic proxy that points to the web service provider deployed on WebSphere Application Server V7. When this Spring bean is invoked, the handler massages the outgoing message:

Listing 6. Client-side SpringTest
package com.ibm.example.client;
import com.ibm.example.proxy.HelloWorld;
import com.ibm.example.proxy.HelloWorldService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {

  public static void main(String[] args) {
		
    try {
      ApplicationContext springCTX = new 
      ClassPathXmlApplicationContext("config/clientSpring.xml");
      HelloWorldProvider port = (HelloWorldProvider) springCTX.getBean("helloProxy");
      String resp = port.sayHello("Griffith");
      System.out.println("[response] " + resp);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
}

Note: The SpringTest client also needs the JAAS configuration shown Figure 8.


Summary

Message-level security is necessary for a variety of projects, and this article series has taught you many facets of using WebSphere Application Server V7 to support your message-level security needs. This third article showed how to use the WSS APIs to take programmatic control over how the client builds SOAP messages that comply with the security requirements of a web service provider. That process allows you to dynamically change the username/password pair, for example, in the SOAP header, or perhaps to dynamically choose keys based on the user instance. Additionally, this article showed how Spring clients can use the WSS APIs to consume web services protected with message-level security.


Acknowledgment

The authors would like to give special thanks to Bill Dodd and Ching-Yun Chao for their diligent review of this article and code.


Download

DescriptionNameSize
Sample project codejaxws_wssapi.zip2.5MB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere, SOA and web services
ArticleID=548271
ArticleTitle=Message-level security with JAX-WS on WebSphere Application Server V7, Part 3: Programmatic client control using Web Services Security APIs
publish-date=08312010