Tuesday, September 8, 2009

SAML Bearer Confirmation - An example using OWSM Client Policy

This is an extension of the discussion started by Brian in his inaugural post here at the FusionSecurity blog. Brian and I, along with other members of the A-Team were out at HQ getting some training on the SOA Security capabilities in 11g, and I wanted to share a useful "How-To" from that week.

First, let me show you the code, and then I'll take you through the code in some details and explain the additional set-up required to make this use case work. Basically, the code is for a stand-alone Java client using OWSM client policies to enable a JAX-WS Web Service Proxy to use SAML Bearer over SSL - quite a mouthful!

The Code


package com.oracle.team2.view;

import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.xml.namespace.QName;
import javax.xml.ws.WebServiceFeature;
import javax.xml.ws.WebServiceRef;
import oracle.security.jps.JpsContext;
import oracle.security.jps.JpsContextFactory;
import oracle.security.jps.service.login.LoginService;

public class MyWebServicePortClient {
@WebServiceRef
private static MyWebServiceService myWebServiceService;


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

URL localURL =
new URL("https://.../MyWebServicePort?WSDL");

QName name =
new QName("http://view.team2.oracle.com/", "MyWebServiceService");

myWebServiceService = new MyWebServiceService(localURL, name);


JpsContextFactory factory =
JpsContextFactory.getContextFactory();

JpsContext jpsContext = factory.getContext("Client");
LoginService loginService =
jpsContext.getServiceInstance(LoginService.class);


CallbackHandler cbh = new MyAssertionCallbackHandler("josh");

String[] selectiveModules = new String[] {
"user.assertion.loginmodule"
};
LoginContext ctx =
loginService.getLoginContext(new Subject(), cbh, selectiveModules);
ctx.login();
Subject s = ctx.getSubject();

final weblogic.wsee.jws.jaxws.owsm.SecurityPolicyFeature securityFeature =
new weblogic.wsee.jws.jaxws.owsm.SecurityPolicyFeature(
"policy:oracle/wss_saml_token_bearer_over_ssl_client_policy");


Subject theSubject =
Subject.getSubject(AccessController.getContext());

System.out.println("The Subject is " + theSubject);


AccessControlContext acc =
AccessController.getContext();

Subject.doAs(s, new PrivilegedAction() {


public Object run() {
WebServiceFeature[] features =
new WebServiceFeature[] { securityFeature };

MyWebService myWebService =
myWebServiceService.getMyWebServicePort(features);

// Add your code to call the desired methods.
System.out.println(
myWebService.helloThere("From a Java client"));

return "done";
}
});

}
}


The Explanation



We're applying OWSM client side policy, that requires a SAML Assertion - Bearer and that the request is over SSL. The SSL implementation is the standard J2SE and OWSM uses the JAAS Subject for the identity. The policy is applied via the WebServiceFeature and is described in the OWSM documentation.

Basically, we're using OPSS inside of a J2SE client to establish the JAAS Subject, without providing a password. I used the OPSS documentation as a reference. I used exactly the MyAssertionCallbackHandler from the documentation.

The final "trick" is that the call to the web service is done inside of a call to Subject.doAs(Subejct,PriviledgedAction). This pushes the Subject from the LoginModule on to call stack, and makes it available for the OWSM policy.

The Configuration


So, there a few command line arguments that need to be passed to this client:

-Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=123456

There are the standard J2SE SSL parameters. The JAX-WS client uses the standard SSL.
In my example, I just used the DemoIdentity and DemoTrust, so I set my -D's to

-Djavax.net.ssl.trustStore=C:\\Oracle\\Middleware\\wlserver_10.3\\server\\lib\\DemoTrust.jks -Djavax.net.ssl.trustStorePassword=DemoTrustKeyStorePassPhrase

You also have to pass the program the location of the jps-config.xml.

-Doracle.security.jps.config=c:\jps-config-jse.xml

The jps-config.xml defines the Oracle Platform Security (OPSS) services used by the client. In our use-case, we're going to be using the LoginService to establish an identity for the client. I got the basis of the jps-config-jse.xml from DOMAIN_HOME\config\fmwconfig\jps-config.jse.xml.

At the end of this file, you need to add a jpsContext called Client that contains a reference to the user.assertion.loginmodule


<jpsContext name="Client">
<serviceInstanceRef ref="user.assertion.loginmodule"/>
</jpsContext>


The user assertion login module allows the client to create an identity for the user with no password. It won't work with any username - the user still has to be found in the identity store, which in this case is stored inside of the system-jazn-data.xml. By default, the login module is looking for the system-jazn-data.xml found in the same directory in the jps-config.xml.

I found the system-jazn-data.xml reference useful in building this file:


<jazn-data>
<jazn-realm>
<realm>
<name>jazn.com</name>
<users>
<user>
<name>josh</name>
<guid>1</guid>
</user>
</users>
</realm>
</jazn-realm>
</jazn-data>


The final, configuration step is to make sure that the user that you're asserting - in this case "josh" exists on the server side in WLS. You can just add the user via the WLS admin console.

The Wrap Up


This is definitely a little tricky, but it demonstrates how to use a SAML Bearer assertion. There is no PKI required on the client side - simply the ability to trust the server at the transport level. This is pretty straight forward and simpler then setting-up either sender-vouches or holder-of-key. Consider using this type of approach from within a trusted network.

2 comments:

  1. Hi Guys,
    According to the documentation:

    http://download.oracle.com/docs/cd/E15523_01
    /apirefs.1111/e13952/taskhelp/webservices/ConfigureWSPolicyFile.html

    you can associate a policy at the end-point level, or the operation level.

    I'm wondering if the service implements policy at the operation level, what is required from a client perspective to set SecurityPolicyFeature just for a specific operation?

    Or, does one have to continually keep requesting a new port with the appropriate feature for each operation?

    e.g.

    // operation without policy
    StockMarketPortType port = service.getStockMarketPortTypePort();

    Integer stockprice = port.getStockPrice("ORCL");

    // if ORCL stock price looks good ... :)

    // operation with SAML policy
    SecurityPolicyFeature securityFeatures[] =
    {
    new SecurityPolicyFeature("oracle/wss11_saml_token_with_message_protection_client_policy")
    };
    port = service.getStockMarketPortTypePort(securityFeatures);
    boolean result = port.sellStock("ORCL", 500);


    thanks

    Matt.

    ReplyDelete
  2. Matt,

    OWSM does not support policies on the operation level....only at the port level. If you wanted to do something custom, take a look at Dynamic Client Policy post. You could write a simple custom assertion that could, based on the operation, apply different client policies.

    ReplyDelete

Note: Only a member of this blog may post a comment.