Tuesday, September 25, 2012

Attaching OWSM policies to JRF-based web services clients

I've recently came across a question in one of our internal mailing lists where a person was under the impression that he would have to write code to propagate the identity when making a web service call using OWSM policies. My answer was something like: "depending on the type of your client you may have to write some very small piece of code to attach a policy, but you should not write code at all to either retrieve the executing client identity or to do the propagation itself". Fortunately, I had an unpublished article that applied 100% to his use case. And here it is now (a little bit revamped).

OWSM (Oracle Web Services Manager) is Oracle's recommended method for securing SOAP web services. It provides agents that encapsulate the necessary logic to interact with the underlying software stack   on both service and client sides. Such agents have their behavior driven by policies. OWSM ships with a bunch of policies that are adequate to most common real world scenarios.

Applying policies to services and clients is usually a straightforward task and can be accomplished in different ways. This is well described in the OWSM Administrators Guide. Looking from the client perspective, the docs describe how to attach policies to SOA references, connection-based clients (typically ADF-based clients) and standard Java EE-based clients using either Enterprise Manager or wlst.

Oracle FMW components (like OWSM agents) are typically deployed on top of a thin software layer called JRF (Java Required Files), providing for the required interoperability with software stacks from different vendors.

This post is a step-by step showing how to code a JRF-based client and attach OWSM policies to it at development-time using Oracle JDeveloper.



This is a 3-step process:

a) Create proxy-supporting classes;
b) Use the right client programming model;
c) Attach OWSM policy to the client;

1. Creating proxy-supporting classes


Very straightforward.
Select a project and go to File -> New and select Web Services in the Business Tier category. On the right side, choose Web Service Proxy and follow the wizard.

proxy1

2. Picking the right client programming model


For clients on 11g R1 PS2 and later, use oracle.webservices.WsMetaFactory. If your client is going to run on 11g R1 or 11g R1 PS1, use the deprecated oracle.j2ee.ws.common.jaxws.ServiceDelegateImpl. All these classes are available in jrf-client.jar file, located at oracle_common/modules/oracle.jrf_11.1.1 folder in your middleware home installation.

2.1 Code sample using oracle.j2ee.ws.common.jaxws.ServiceDelegateImpl (up to 11g PS1)

...
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.BindingProvider;
import oracle.j2ee.ws.common.jaxws.ServiceDelegateImpl;
import oracle.webservices.ClientConstants;
import oracle.wsm.security.util.SecurityConstants;
import org.w3c.dom.Element;
...
String endpoint = "http://127.0.0.1:7101/B7aFusionWebServices-Model-context-root/MyAppModuleService";
URL wsdlURL = new URL(endpoint+"?WSDL");
ServiceDelegateImpl serviceDelegate = new ServiceDelegateImpl(wsdlURL, new QName("http://xmlns.oracle.com/oracle/apps/", "MyAppModuleService"), oracle.webservices.OracleService.class);
MyAppModuleService port = serviceDelegate.getPort(new QName("http://xmlns.oracle.com/oracle/apps/", "MyAppModuleServiceSoapHttpPort"), MyAppModuleService.class );
 
InputStream isClientPolicy = <Your_Client_Class_Name>.class.getResourceAsStream("client-policy.xml");
Map<String,Object> requestContext = ((BindingProvider) port).getRequestContext();
requestContext.put(ClientConstants.CLIENT_CONFIG, fileToElement(isClientPolicy));
requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint);

// Add other properties here. For identity switching add
// requestContext.put(SecurityConstants.ClientConstants.WSS_CSF_KEY, "<AppID_csf_key>");
port.sayHello(name);
...
 
// Utility method to convert an InputStream into an org.w3c.dom.Element 
public static Element fileToElement(InputStream f) throws IOException, Exception {
  DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
  builderFactory.setValidating(false);
  builderFactory.setNamespaceAware(true);
  builderFactory.setIgnoringElementContentWhitespace(true);
  builderFactory.setIgnoringComments(true);
  return builderFactory.newDocumentBuilder().parse(f).getDocumentElement();
}

In this sample, MyAppModuleService is the port interface generated by JDeveloper in the step before.

The parameters to the QName object constructor are the target namespace and the service/port name, both found in the web service wsdl.

2.2 Code sample using oracle.webservices.WsMetaFactory (11g PS2 +)

...
import java.net.URL;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import oracle.webservices.ClientConstants;
import oracle.webservices.ImplType;
import oracle.webservices.WsMetaFactory;
import oracle.wsm.security.util.SecurityConstants;
import org.w3c.dom.Element;
...

String endpoint = "http://localhost:7101/WebServiceSample2-WebService-context-root/GreetingPort";
URL serviceWsdl = new URL(endpoint + "?wsdl");
QName serviceQName = new QName("http://sample2.webservice/","GreetingService");
QName portQName = new QName("http://sample2.webservice/","GreetingPort");


Service proxyService = WsMetaFactory.newInstance(ImplType.JRF).createClientFactory().create(serviceWsdl, serviceQName);
Greeting port = proxyService.getPort(portQName, Greeting.class);

InputStream clientPolicyStream = Servlet1.class.getResourceAsStream("client-policy.xml");
Element clientConfigElem = this.fileToElement(clientPolicyStream);

Map<String,Object> requestContext = ((BindingProvider) port).getRequestContext();
requestContext.put(ClientConstants.CLIENT_CONFIG , clientConfigElem);
requestContext.put(SecurityConstants.ClientConstants.WSS_CSF_KEY,"servlet1-key");

port.sayHello();

In this sample, Greeting is the port interface generated by JDeveloper in the step before.

The parameters to the QName object constructor are the target namespace and the service/port name, both found in the web service wsdl.


3. Attaching the OWSM policy


The OWSM policy is passed as an org.w3c.dom.Element to the requestContext Map. One way to come up with such Element is through an XML file that contains a reference to the actual OWSM client-side policy to be used.


InputStream clientPolicyStream = Servlet1.class.getResourceAsStream("client-policy.xml");
Element clientConfigElem = this.fileToElement(clientPolicyStream);
Map<String,Object> requestContext = ((BindingProvider) port).getRequestContext();
requestContext.put(ClientConstants.CLIENT_CONFIG , clientConfigElem);


Here are the XML file (client-policy.xml) contents. You have to create a file like this and make it available to your application CLASSPATH. It references the OWSM policy to be given to the requestContext Map.

<?xml version="1.0" encoding="UTF-8"?>
<oracle-webservice-clients>
 <webservice-client>
   <port-info>
     <policy-references>
       <policy-reference uri="oracle/wss11_saml_token_client_policy" category="security"/>
     </policy-references>
   </port-info>
 </webservice-client>
</oracle-webservice-clients>

For SAML-based identity propagation, use any of the SAML client policies.

In this case, the policy retrieves the user name Principal from the Java Subject who is running the client and adds it to a generated SAML token in the SOAP call header.

3.1. Switching Identities

To propagate a new identity rather than the one executing the client, use a username token-based policy. By the way, the sample using oracle.webservices.WsMetaFactory may use oracle/wss_username_token_client_policy as the policy name in order to propagate the identity referred by the servlet1-key csf key.

Map<String,Object> requestContext = ((BindingProvider) port).getRequestContext(); 
requestContext.put(SecurityConstants.ClientConstants.WSS_CSF_KEY, "servlet1-key");

"servlet1-key” must match a key entry in the domain-level credential store that holds the username/password pair required for the use case implementation. Here's how you create a key for OWSM usage in the credential store using wlst:

wls:/offline> connect()
Please enter your username :weblogic
Please enter your password :
Please enter your server URL [t3://localhost:7001] :t3://localhost:7101
Connecting to t3://localhost:7101 with userid weblogic 
...
Successfully connected to Admin Server 'DefaultServer' that belongs to 
domain 'DefaultDomain'.
Warning: An insecure protocol was used to connect to the
server. To ensure 
on-the-wire security, the SSL port or
Admin port should be used instead.
wls:/DefaultDomain/serverConfig> 
createCred(map="oracle.wsm.security",key="servlet1-key",user="weblogic",password="welcome1")

In this case, the policy retrieves the username/password pair from the credential store and adds it to a generated username token in the outgoing SOAP header.

What if the client is a Java SE application?


So far so good, but what happens when the client runs on a Java SE environment?

a) How do you get a hold of the OWSM policy?

Add oracle_common/modules/oracle.wsm.policies_11.1.1/wsm-seed-policies.jar, available in your middleware home installation, to the client CLASSPATH. All policy files are in it.

b) How to deal with the credential store (for identity switching) ?

You need to supply the client with 2 files:

1 - cwallet.sso, which is a file-based credential store. In order to author cwallet.sso, I recommend that you use wlst's createCred command. Yes, you'll need a JRF-enabled Weblogic server to create it.

2 - jps-config.xml, through -Doracle.security.jps.config Java option. In jps-config.xml, make sure there's a credential store service instance available for the default context pointing to the folder where cwallet.sso is located, as shown in this sample jps-config.xml:


<?xml version = '1.0' encoding = 'UTF-8'?>
<jpsConfig xmlns="http://xmlns.oracle.com/oracleas/schema/11/jps-config-11_1.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/oracleas/schema/11/jps-config-11_1.xsd">
   <property value="doasprivileged" name="oracle.security.jps.jaas.mode"/>
   <propertySets/>
   <serviceProviders>
      <serviceProvider class="oracle.security.jps.internal.credstore.ssp.SspCredentialStoreProvider" name="credstore.provider" type="CREDENTIAL_STORE">
         <description>Credential Store Service Provider</description>
      </serviceProvider>
      <serviceProvider class="oracle.security.jps.internal.idstore.xml.XmlIdentityStoreProvider" name="idstore.xml.provider" type="IDENTITY_STORE">
         <description>XML-based IdStore Provider</description>
      </serviceProvider>
      <serviceProvider class="oracle.security.jps.internal.policystore.xml.XmlPolicyStoreProvider" name="policystore.xml.provider" type="POLICY_STORE">
         <description>XML-based PolicyStore Provider</description>
      </serviceProvider>
      <serviceProvider class="oracle.security.jps.internal.login.jaas.JaasLoginServiceProvider" name="jaas.login.provider" type="LOGIN">
         <description>Login Module Service Provider</description>
      </serviceProvider>
      <serviceProvider class="oracle.security.jps.internal.keystore.KeyStoreProvider" name="keystore.provider" type="KEY_STORE">
        <description>PKI Based Keystore Provider</description>
        <property value="owsm" name="provider.property.name"/>
      </serviceProvider>
   </serviceProviders>
   <serviceInstances>
      <serviceInstance provider="credstore.provider" name="credstore">
         <property value="./" name="location"/>
      </serviceInstance>
      <serviceInstance provider="idstore.xml.provider" name="idstore.xml">
         <property value="./jazn-data.xml" name="location"/>
         <property value="jazn.com" name="subscriber.name"/>
      </serviceInstance>
      <serviceInstance provider="policystore.xml.provider" name="policystore.xml">
         <property value="./jazn-data.xml" name="location"/>
      </serviceInstance>
      <serviceInstance provider="jaas.login.provider" name="idstore.loginmodule">
         <property value="oracle.security.jps.internal.jaas.module.idstore.IdStoreLoginModule" name="loginModuleClassName"/>
         <property value="REQUIRED" name="jaas.login.controlFlag"/>
         <property value="true" name="debug"/>
         <property value="true" name="addAllRoles"/>
         <property value="false" name="remove.anonymous.role"/>
      </serviceInstance>
      <serviceInstance location="./default-keystore.jks" provider="keystore.provider" name="keystore">
        <description>Default JPS Keystore Service</description>
        <property value="JKS" name="keystore.type"/>
        <property value="oracle.wsm.security" name="keystore.csf.map"/>
        <property value="keystore-csf-key" name="keystore.pass.csf.key"/>
        <property value="sign-csf-key" name="keystore.sig.csf.key"/>
        <property value="enc-csf-key" name="keystore.enc.csf.key"/>
     </serviceInstance>
   </serviceInstances>
   <jpsContexts default="default">
      <jpsContext name="default">
         <serviceInstanceRef ref="credstore"/>
         <serviceInstanceRef ref="idstore.xml"/>
         <serviceInstanceRef ref="policystore.xml"/>
         <serviceInstanceRef ref="idstore.loginmodule"/>
         <serviceInstanceRef ref="keystore"/>
      </jpsContext>
   </jpsContexts>
</jpsConfig>

Hope this helps some of you dear readers out there.

No comments:

Post a Comment

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