How To Implement OAuth Signed Fetch Services and Modules

For information on how to configure the services and modules, see How To Configure OAuth Signed Fetch Services and Modules.
For other samples of Micro Applications, see MicroApplications Samples

Overview

Sample implementation uses combination of JAAS modules and Web Application for enabling SSO using the OpenSocial container signed request, as specified by OpenSocial API:

Basic authentication flow is outlined in Enterprise Technical User Guide, Appendix - Single Sign-On Using Signed Fetch. This page covers in details what and how happens in the step #3 - signed request validation and ticket issuing using the reference implementation.

Components of the request processing:

Sequence of actions:

In the following sections the step-by-step instructions are provided for implementing the logon and authentication sequence.

Implementing Login Modules

Contents:

Preparing Third-Party Libraries

OAuth

In this sample, for the OAuth validation implementation the library from http://oauth.net will be used. This library provides the ready to use code for most OAuth related routines.

This code requires Java 5.0 JDK - while it is supported on NetWeaver CE 7.1, it won't work on NetWeaver 7.0. It is possible to do the code downgrade to JDK 1.4 compatibility level. The following adjustments are required:

  • Replace all generic types with the non-generic by adding the required class-casts:
    Before:
    final Map<String, String> pMap = OAuth.newMap(parameters);
    String signatureMethod = pMap.get("oauth_signature_method");
    

    After:

    final Map pMap = OAuth.newMap(parameters);
    String signatureMethod = (String)pMap.get("oauth_signature_method");
    
  • Replace enhanced "for" loop with regular "for" loop:
    Before:
    public static void formEncode(Iterable<? extends Map.Entry> parameters) {
    ....
    	for (Map.Entry parameter : parameters) {
    		parameter.getKey(); 
    		....
    	}
    ....
    }
    

    After:

    public static void formEncode(List parameters)  {
    ....
    	for (Iterator i = parameters.iterator();i.hasNext();) {
    		Map.Entry parameter = (Map.Entry)i.next();
    		parameter.getKey(); 
    		....
    	}
    ....
    }
    
  • Implement custom classes instead of Enums:
    Before:
    public enum ParameterStyle {
    	AUTHORIZATION_HEADER, BODY, QUERY_STRING;
    };
    ... 
    ParameterStyle style = ParameterStyle.BODY;
    

    After:

    public class ParameterStyle {
    	public static final String AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER";
    	public static final String BODY = "BODY";
    	public static final String QUERY_STRING = "QUERY_STRING";
    	public String value = null;
    	
    	public ParameterStyle(String style) {
    		this.value = style;
    	}
    };
    ...
    ParameterStyle style = new ParameterStyle(ParameterStyle.BODY);
    
  • Replace varargs with argument arrays:
    Before:
    public void requireParameters(String... names)
    

    After:

    public void requireParameters(String[] names)
    
  • Remove any metadata (annotations)
    Before:
    @Override
    public void setConsumerSecret(String consumerSecret) {
    

    After:

    public void setConsumerSecret(String consumerSecret) {
    

Code sample in this section assumes usage of the OAuth library from https://oauth.googlecode.com/svn revision 1095, with applied modifications mentioned above;

JSON

Since the sample implementation assumes output in the JSON format, we will use the pre-build JSON library from json.org. Since the json.org provides JSON implementation only in the source format, we need to compile and package classes in the JAR file - for this demo we assume it to be packaged in the json.jar java archive.

Implementing Modules for NW 7.0

Creating a Project Structure

For the OAuth validation login modules implementation we need to create the following projects:

  • OAuthLoginModules - Java type project that will contain all custom java classes, login modules;
  • OAuthLoginModulesLibrary - Library Project type project for wrapping the OAuthLoginModules project into deployable SDA library.

JAAS login modules will be placed in the package corp.sap.pal.widgets.oauth.jaas

For the OAuthLoginModules project we need to set additional libraries in the classpath. First, we need third-party libraries described in the Preparing Third-Party Librariessection: the OAuth14.jar file to be added to the classpath.

The set of SAP and J2EE standard libraries is required. To add standard libraries to the classpath, proceed as follows:

  1. In J2EE Explorer, right click the OAuthLoginModules project.
  2. Select Add/Remove Additional Libraries..
  3. Expand the Installed Libraries/sap.com tree path.
  4. Select the following libraries:
    • com.sap.security.api.sda
    • j2eeca
    • kernel.sda
    • keystore_api
    • security_api
    • servlet

Validating the OAuth Signature

First login module in the JAAS authentication sequence is Validation Module. This module will validate that incoming HTTP request has been signed by trusted peer according to the OAuth specification. Hence, this module can work only if there is incoming HTTP request, i.e., we can access the HTTPServletRequest object. To get access to the HTTP request, we will use the RequestCallback callback that has to be supported by the logon sequence initiator.

For this logon module the following customization options are available:

Option name Description
keystore_name As request, is signed with the digital signature, validation module must get access to the Certificate object for validating this signature. For this module SAP NetWeaver keystorewill is used, where Certificate will be looked up by its public key
oauth_paramter By default user is identified by the OAuth request parameter opensocial_user_id, but optionally, for some OpenSocial containers some other OAuth request parameter may be uses. This option allows to override default OAuth user mapping attribute name with the custom one
url_override In some network setups (e.g., with the use of reverse proxy on the domain), the URL in HTTPServletRequest might differ from the original one used by the OpenSocial container for the request signing. By default in this case signature verification would fail due to URL modifications. This option allows overriding the URL to the original issuer URL for the validation purposes

Steps for creating the JAAS validation login module:

  1. In project OAuthService, create the corp.sap.pal.widgets.oauth.jaas.OAuthValidationModule class, specifying its superclass to be com.sap.engine.interfaces.security.auth.AbstractLoginModule. Allow inherited abstract method stubs to be created, to generate stubs for JAAS login, commit, abort and logout to be created
  2. Create some private variables that will be required during the logon sequence execution:
    • private CallbackHandler callbackHandler - a reference to the callback handler for retrieving additional information required by the logon module.
    • private Map sharedState - a state object shared between all logon modules in the configured stack, used to transfer data from one module to next.
    • private Map options - map of configuration options for the logon module.
    • private boolean successful - a state of authentication.
  3. Implement JAAS module method: initialize to initialize variables defined in the previous step. This method will be called by the JAAS layer:
    public void initialize(	Subject subject, CallbackHandler callbackHandler,
    			Map sharedState, Map options) {
    
    	super.initialize(subject, callbackHandler, sharedState, options);
    
    	// Initialize all local variables defined in previous step
    	this.callbackHandler = callbackHandler;
    	this.sharedState = sharedState;
    	this.options = options;
    	this.successful = false;
    }
    
  4. Also we will create a helper method to retrieve certificate from the keystore by it's public key:
    private Certificate getCert(String alias, String keystoreName)
    	throws OAuthException {
    	try {
    		InitialContext ctx = new InitialContext();
    		KeystoreManager manager = (KeystoreManager) ctx.lookup("keystore");
    		KeyStore keyStore = manager.getKeystore(keystoreName);
    		Certificate c = keyStore.getCertificate(alias.replace('.', '-'));
    		return c;
    	} catch (Exception e) {
    		throw new OAuthException(
    			"Failed to get Certificate for alias: " + alias, e);
    	}
    }
    
  5. Lets now create the actual signature validation method private OAuthMessage verifyOAuthMessage(HttpServletRequest request) throws OAuthException:
    // Create OAuth validation objects required for validation. 
    // See <a href="http://oauth.net/">oauth.net</a> for details
    OAuthMessage message = OAuthServlet.getMessage(
    		request,
    		(String) options.get("url_override"));
    OAuthServiceProvider provider =
    	new OAuthServiceProvider(null, null, null);
    OAuthConsumer consumer =
    	new OAuthConsumer(null, message.getConsumerKey(), null, provider);
    
    
    // Get certificate and public key of the container signature.
    // Certificate matching the public key has to be securely acquired
    // from container provider and stored in NetWeaver key store 
    // during system configuration
    
    // read public key of the certificate used for signing from OAuth 
    // parameters
    message.requireParameters(
    	new String[] { "xoauth_signature_publickey" });
    String publickey = message.getParameter("xoauth_signature_publickey");
    
    // read keystore name from JAAS parameters
    String keystore =(String) options.get("keystore_name");
    if (keystore == null)
    	throw new OAuthException(
    		"Option \"keystore_name\" is not set");
    
    // Get the certificate
    Certificate cert = getCert(publickey, keystore);
    if (cert == null)
    	throw new OAuthException(
    		"Certificate for alias: " + publickey + " not found");
    consumer.setProperty(RSA_SHA1.X509_CERTIFICATE, cert);
    
    // Using retrieved certificate, call OAuth library for 
    // signature validaito
    OAuthAccessor accessor = new OAuthAccessor(consumer);
    OAuthValidator validator = new OpenSocialOAuthValidator();
    // If signature is not valid or there are any other issues 
    // with the request validity, following line will throw 
    // exception
    message.validateMessage(accessor, validator);
    
    // validation successful - return message object
    return message;
    
  6. And finally we need to put it all together. As for the JAAS specification, the public boolean login() throws LoginException method is called to initiate the signature validation:
    // Get access to HttpRequest object using custom callbacks. For this code 
    // to work, caller initiating JAAS login must support callback of type 
    // WebCallback.
    WebCallback callback = new WebCallback();
    
    Callback[] callbacks = new Callback[] { callback };
    callbackHandler.handle(callbacks);
    
    HttpServletRequest request = callback.getRequest();
    
    // Perform valildation of request signature
    OAuthMessage oauth = null;
    try {
    	// method verifyOAuthMessage(request) performs actual validation
    	oauth = verifyOAuthMessage(request);
    } catch (OAuthProblemException e) {
    	// throw login exception if 
    	throwUserLoginException(e);
    }
    
    // if at this point exception hasn't been thrown, it means that validation is 
    // successful and module needs to pass OpenSocial user identifier to next login 
    // module using shard context. 
    
    // By default OAuth paramter opensocial_viewer_id is used as user identifier, but 
    // this could be overriden to any other paramter (e.g., opensocial_viewer_email), 
    // using JAAS login module properties
    String oauthParam = (String) options.get("oauth_paramter");
    if (oauthParam == null || oauthParam.trim().length() == 0) {
    	oauthParam = "opensocial_viewer_id";
    }
    
    // set attribute value in shard state
    sharedState.put(
    	OAuthUserMappingModule.SHARED_MAPPING_PARAMETER,
    	oauth.getParameter(oauthParam));
    	
    // set internal flag that login was successful
    successful = true;
    

For more information on the OAuth specification, see http://oauth.net/core/1.0/ - signing_process.

Performing the User Mapping

Next JAAS module to process the logon sequence is User Mappging Module. For this we create a class corp.sap.pal.widgets.oauth.jaas.OAuthUserMappingModule extends com.sap.engine.interfaces.security.auth.AbstractLoginModule. This module is required because GUID of the OpenSocial container user most likely doesn't match NetWeaver user data source name. It allows mapping user ID from OpenSocial container to UME using System Landscape Objects (see SAP System Landscape help page for more details)

For this logon module we have the following configurable option:

Option name Description
system_alias System landscape object alias to be queried for user mapping

Same as for validation module, main functionality is in the public boolean login() throws LoginException method:

// get OpenSocial container user GUID. It is expected that previous module
// in stack has set it in attribute defined by constant: SHARED_MAPPING_PARAMETER
// Report an error if it is not there
String uid = (String) sharedState.get(SHARED_MAPPING_PARAMETER);
if (uid == null) {
	throwNewLoginException("No shard state user ID provided!");
}

// Find system for user mapping by reading JAAS module parameter and 
// looing it up in system landscape

// get system name
String systemAlias = (String) options.get("system_alias");

ArrayList systemLandscapes =
	UMFactory.getSystemLandscapeWrappers();
	
// Until now, there's only one system landscape implementation available:
// The system landscape which is part of SAP Enterprise Portal.
ISystemLandscapeWrapper systemLandscape =
	(ISystemLandscapeWrapper) systemLandscapes.get(0);

// Get user mapping system object, report an error if system not found
ISystemLandscapeObject system =
	systemLandscape.getSystemByAlias(systemAlias);
if (system == null) {
	throwNewLoginException("Failed to get system by alias name \"" + systemAlias + "\"");
} 

// Do reverse lookup. 
// System has to have "Logon Method" set to "UIDPW" for reverse lookup to work
IUserMapping userMapping = UMFactory.getUserMapping();
String userId = userMapping.getInverseMappingData(uid, system);
if (userId == null) {
	// If there is no mapping found for UID returned from OpenSocial container, 
	// fail login sequence will logon exception
	throwNewLoginException("User for uid: " + uid + " not found in system:" + systemAlias);
} 

// Need to convert from user UID to logon name for subsequent modules to 
// be able to correctly interpret it
userName = UMFactory.getUserFactory().getUser(userId).getUniqueName(); 
if (userName == null) {
	throwNewLoginException("User for uid: " + uid + " not found");
} 

// If no exception was thrown at this point, the user-mapping 
// has been successful.
// Set found user name in shared state so that 
// following CreateTicketLoginModule can pick it up
sharedState.put(AbstractLoginModule.NAME, userName);

// Set internal state to success
successful = true;

Configuration and Deployment

Steps to complete the package creation:

  1. The OAuthLoginModules project contains two classes: corp.sap.pal.widgets.oauth.jaas.OAuthValidationModule and corp.sap.pal.widgets.oauth.jaas.OAuthUserMappingModule, we need to generate the jar file containing these classes. The jar file will be used by the OAuthLoginModulesLibrary project in order to build the SDA archive.
    • Select OAuthLoginModules and bring up the context menu. Select Export > JAR file. Select the src directory to be included in the jar file, specify the jar file name, for example OAuthLoginModules.jar and click finish. The OAuthLoginModules.jar jar file is created.
  2. Configure the OAuthLoginModulesLibrary project.
    • Add jar files to the library. In the J2EE Explorer view, select the _OAuthLoginModulesLibrary/server/provider.xml file. Select the Jars tab and add the following libraries:
      • OAuth third-party library build in Preparing 3rd Party Libraries section. In our example OAuth14.jar,
      • OAuthLoginModule.jar classes build in the OAuthLoginModules project,
      • commons-httpclient-3.1.jar and commons-codec-1.3.jar libraries used to compile OAuth14.jar
    • Add reference libraries. Select the References tab and add the following external references:
    • In the J2EE Explorer view, build the library by selecting + Build Library Archieve from the context menu . New file appears in the OAuthLoginModulesLibrary.sda directory structure. Now you can deploy it by selecting it and choosing Deploy to J2EE engine from the context menu.

Implementing Modules for NW 7.1

Creating a Project Structure

For the sample OAuth validation implementation we need to create the following projects:

  • OAuthLoginModules - Java type project that will contain all custom java classes, login modules;
  • OAuthLoginModulesEAR - Enterprise Application Project type project for wrapping OAuthLoginModules project into deployable component.

JAAS login modules will be placed in the corp.sap.pal.widgets.oauth.jaas package.

For OAuthLoginModules we need to set additional libraries in the classpath. First, we need our third-party libraries described in section Preparing Third-Party Libraries: OAuth14.jar and JSON.jar to be added to the classpath.

The set of SAP and J2EE standard libraries is required. To add standard libraries to the classpath, proceed as follows:

  1. In J2EE Explorer, right click the OAuthLoginModules project.
  2. Select Add/Remove Additional Libraries..
  3. Expand the Installed Libraries/sap.com tree path.
  4. Select the following libraries:
    • com.sap.security.api.sda
    • com.sap.security.core.sda
    • j2eeca
    • kernel.sda
    • keystore_api
    • security_api
    • servlet

Validating the OAuth Signature

First login module in the JAAS authentication sequence is Validation Module. This module will validate that incoming HTTP request has been signed by trusted peer according to OAuth specification. Hence, this module can work only if there is incoming HTTP request, i.e., we can access HTTPServletRequest object. To get access to HTTP request, we will use RequestCallback callback that has to be supported by logon sequence initiator.

For this logon module we have following customization options:

Option name Description
keystore_name As the request is signed with the digital signature, validation module must get access to the Certificate object for validating this signature. For that module the SAP NetWeaver keystore is used, where Certificate will be looked up by its public key
oauth_paramter By default user is identified by the opensocial_user_id OAuth request parameter , but optionally, for some OpenSocial containers some other OAuth request parameter may be uses. This option allows to override default OAuth user mapping attribute name with the custom one
url_override In some network setups (e.g., with the use of reverse proxy on the domain), the URL in HTTPServletRequest might differ from the original one used by OpenSocial container for request signing. By default in this case signature verification would fail due to URL modifications. This option allows overriding URL to the original issuer URL for the validation purposes

Steps for creating the JAAS validation login module:

  1. In the OAuthService project, create the corp.sap.pal.widgets.oauth.jaas.OAuthValidationModule class, specifying its superclass to be com.sap.engine.interfaces.security.auth.AbstractLoginModule. Allow inherited abstract method stubs to be created, to generate stubs for JAAS login, commit, abort and logout to be created
  2. Create some private variables that will be required during the logon sequence execution:
    • private CallbackHandler callbackHandler - a reference to the callback handler for retrieving additional information required by logon module.
    • private Map sharedState - a state object shared between all logon modules in configured stack, used to transfer data from one module to next
    • private Map options - map of the configuration options for the logon module.
    • private boolean successful - a state of authentication
  3. Implement the initialize JAAS module method to initialize variables defined in the previous step. This method will be called by the JAAS layer:
    public void initialize(	Subject subject, CallbackHandler callbackHandler,
    			Map sharedState, Map options) {
    
    	super.initialize(subject, callbackHandler, sharedState, options);
    
    	// Initialize all local variables defined in previous step
    	this.callbackHandler = callbackHandler;
    	this.sharedState = sharedState;
    	this.options = options;
    	this.successful = false;
    }
    
  4. Also we will create a helper method to retrieve certificate from the keystore by it's public key:
    private Certificate getCert(String alias, String keystoreName)
    	throws OAuthException {
    	try {
    		InitialContext ctx = new InitialContext();
    		KeystoreManager manager = (KeystoreManager) ctx.lookup("keystore");
    		KeyStore keyStore = manager.getKeystore(keystoreName);
    		Certificate c = keyStore.getCertificate(alias.replace('.', '-'));
    		return c;
    	} catch (Exception e) {
    		throw new OAuthException(
    			"Failed to get Certificate for alias: " + alias, e);
    	}
    }
    
  5. Lets now create the private OAuthMessage verifyOAuthMessage(HttpServletRequest request) throws OAuthException actual signature validation method :
    // Create OAuth validation objects required for validation. 
    // See <a href="http://oauth.net/">oauth.net</a> for details
    OAuthMessage message = OAuthServlet.getMessage(
    		request,
    		(String) options.get("url_override"));
    OAuthServiceProvider provider =
    	new OAuthServiceProvider(null, null, null);
    OAuthConsumer consumer =
    	new OAuthConsumer(null, message.getConsumerKey(), null, provider);
    
    
    // Get certificate and public key of the container signature.
    // Certificate matching the public key has to be securely acquired
    // from container provider and stored in NetWeaver key store 
    // during the system configuration
    
    // read public key of the certificate used for signing from OAuth 
    // parameters
    message.requireParameters(
    	new String[] { "xoauth_signature_publickey" });
    String publickey = message.getParameter("xoauth_signature_publickey");
    
    // read keystore name from JAAS parameters
    String keystore =(String) options.get("keystore_name");
    if (keystore == null)
    	throw new OAuthException(
    		"Option \"keystore_name\" is not set");
    
    // Get the certificate
    Certificate cert = getCert(publickey, keystore);
    if (cert == null)
    	throw new OAuthException(
    		"Certificate for alias: " + publickey + " not found");
    consumer.setProperty(RSA_SHA1.X509_CERTIFICATE, cert);
    
    // Using retrieved certificate, call OAuth library for 
    // signature validaito
    OAuthAccessor accessor = new OAuthAccessor(consumer);
    OAuthValidator validator = new OpenSocialOAuthValidator();
    // If signature is not valid or there are any other issues 
    // with the request validity, following line will throw 
    // exception
    message.validateMessage(accessor, validator);
    
    // validation successful - return message object
    return message;
    
  6. And finally we need to put it all together. As for the JAAS specification, the public boolean login() throws LoginException method is called to initiate the signature validation:
    // Get access to HttpRequest object using custom callbacks. For this code 
    // to work, caller initiating JAAS login must support callback of type 
    // WebCallback.
    RequestCallback callback = new RequestCallback();
    
    Callback[] callbacks = new Callback[] { callback };
    callbackHandler.handle(callbacks);
    
    HttpServletRequest request = callback.getRequest();
    
    // Perform valildation of request signature
    OAuthMessage oauth = null;
    try {
    	// method verifyOAuthMessage(request) performs actual validation
    	oauth = verifyOAuthMessage(request);
    } catch (OAuthProblemException e) {
    	// throw login exception if 
    	throwUserLoginException(e);
    }
    
    // if at this point exception hasn't been thrown, it means that validation is 
    // successful and module needs to pass the OpenSocial user identifier to next login 
    // module using shard context. 
    
    // By default OAuth paramter opensocial_viewer_id is used as user identifier, but 
    // this could be overriden to any other paramter (e.g., opensocial_viewer_email), 
    // using JAAS login module properties
    String oauthParam = (String) options.get("oauth_paramter");
    if (oauthParam == null || oauthParam.trim().length() == 0) {
    	oauthParam = "opensocial_viewer_id";
    }
    
    // set attribute value in shard state
    sharedState.put(
    	OAuthUserMappingModule.SHARED_MAPPING_PARAMETER,
    	oauth.getParameter(oauthParam));
    	
    // set internal flag that login was successful
    successful = true;
    

For more information on the OAuth specification, see http://oauth.net/core/1.0/ - signing_process.

Performing the User-Mapping

Next JAAS module to process the logon sequence is User Mappging Module. For this purpose we create the corp.sap.pal.widgets.oauth.jaas.OAuthUserMappingModule extends com.sap.engine.interfaces.security.auth.AbstractLoginModule class . This module is required because GUID of the OpenSocial container user most likely doesn't match the NetWeaver user data source name. It allows mapping user ID from OpenSocial container to UME using System Landscape Objects (see SAP System Landscape help page for more details)

For this logon module we have one configurable option:

Option name Description
system_alias System landscape object alias to be queried for user mapping

Same as for the validation module, main functionality is in the public boolean login() throws LoginException method:

// get the OpenSocial container user GUID. It is expected that previous module
// in stack has set it in attribute defined by constant: SHARED_MAPPING_PARAMETER
// Report an error if it is not there
String uid = (String) sharedState.get(SHARED_MAPPING_PARAMETER);
if (uid == null) {
	throwNewLoginException("No shard state user ID provided!");
}

// Find system for user mapping by reading JAAS module parameter and 
// looing it up in system landscape

// get system name
String systemAlias = (String) options.get("system_alias");

ArrayList systemLandscapes =
	UMFactory.getSystemLandscapeWrappers();
	
// Until now, there's only one system landscape implementation available:
// The system landscape which is part of SAP Enterprise Portal.
ISystemLandscapeWrapper systemLandscape =
	(ISystemLandscapeWrapper) systemLandscapes.get(0);

// Get user mapping system object, report an error if system not found
ISystemLandscapeObject system =
	systemLandscape.getSystemByAlias(systemAlias);
if (system == null) {
	throwNewLoginException("Failed to get system by alias name \"" + systemAlias + "\"");
} 

// Do reverse lookup. 
// System has to have "Logon Method" set to "UIDPW" for reverse lookup to work
IUserMapping userMapping = UMFactory.getUserMapping();
String userId = userMapping.getInverseMappingData(uid, system);
if (userId == null) {
	// If there is no mapping found for UID returned from OpenSocial container, 
	// fail login sequence will logon exception
	throwNewLoginException("User for uid: " + uid + " not found in system:" + systemAlias);
} 

// Need to convert from user UID to logon name for subsequent modules to 
// be able to correctly interpret it
userName = UMFactory.getUserFactory().getUser(userId).getUniqueName(); 
if (userName == null) {
	throwNewLoginException("User for uid: " + uid + " not found");
} 

// If no exception been thrown at this poing, user mapping 
// has been successful.
// Set found user name in shared state so that 
// following CreateTicketLoginModule can pick it up
sharedState.put(AbstractLoginModule.NAME, userName);

// Set internal state to success
successful = true;

Utility Classes

There are two utility calsses: RequestCallbackHandler and RequestCallback which helps to pass the HttpServletRequest object to login modules.
These calsses are placed in the corp.sap.pal.widgets.oauth.jaas.callback package.
RequestCallback extends javax.security.auth.callback.Callback and holds the HttpServletRequest object.

public class RequestCallback implements Callback {
	
	private HttpServletRequest request;

	public HttpServletRequest getRequest() {
		return request;
	}

	public void setRequest(HttpServletRequest request) {
		this.request = request;
	}
}

RequestCallbackHandler implements javax.security.auth.callback.CallbackHandler and is invoked by NW API when login module stack is initialized. RequestCallbackHandler will assign HttpServletRequest object to RequestCallback which then can be used by login modules.

	public RequestCallbackHandler(HttpServletRequest request,
			HttpServletResponse response) {
		this.request = request;
	}

	public void handle(Callback[] arg1) throws IOException,
			UnsupportedCallbackException {
		if(arg1[0] instanceof ) {
			( (RequestCallback) arg1[0]).setRequest(request);
			return;
		}
	}

Configuration and Deployment

Steps for completing the package creation:

  1. To the OAuthLoginModulesEAR project add the LoginModuleConfiguration.xml file:
    • Fill the LoginModuleConfiguration.xml file with login module configuration as shown in this example:
      <?xml version="1.0" encoding="UTF-8"?>
      <login-modules>
      <!-- holds all login modules -->
      <login-module>
      <!-- describes one login module -->
      <display-name>OAuthValidationModule</display-name>
      <!-- holds the display name of the login module -->
      <class-name>corp.sap.pal.widgets.oauth.jaas.OAuthValidationModule</class-name>
      <!-- holds the full path to the login module class -->
      <description>OAuthValidationModule</description>
      <!-- holds the description of the login module -->
      <options>
      <!-- holds all the options of the login module -->
      <option>
      <!-- holds the name/value pair of an option -->
      <name>oauth_paramter</name>
      <value>opensocial_viewer_id</value>
      </option>
      <option>
      <!-- holds the name/value pair of an option -->
      <name>keystore_name</name>
      <value>OAuth</value>
      </option>
      <option>
      <!-- holds the name/value pair of an option -->
      <name>url_override</name>
      <value>http://<host>:<port>/OAuthService/auth</value>
      </option>
      </options>
      </login-module>
      <login-module>
      <!-- describes one login module -->
      <display-name>OAuthUserMappingModule</display-name>
      <!-- holds the display name of the login module -->
      <class-name>corp.sap.pal.widgets.oauth.jaas.OAuthUserMappingModule</class-name>
      <!-- holds the full path to the login module class -->
      <description>OAuthUserMappingModule</description>
      <!-- holds the description of the login module -->
      <options>
      <!-- holds all the options of the login module -->
      <option>
      <!-- holds the name/value pair of an option -->
      <name>system_alias</name>
      <value>OpenSocialAlias</value>
      </option>
      </options>
      </login-module>
      </login-modules>
  2. If not yet done, add OAuthLoginModules project to EAR:
    • Right click OAuthLoginModulesEAR project
    • Choose Add Modules and select OAuthLoginModules module
  3. Create EAR file:
    • Right click OAuthLoginModulesEAR project
    • Choose Build Application Archive

As the result, there is the EAR file created in the root of the OAuthLoginModulesEAR project. See the [SSO Using OAuth] section for the application deployment, configuration, and usage details.

Logon Ticket Responder

As the implemented Login Modules are using custom callback handlers that are not supported by NetWeaver container managed authentication,
we need to implement a service that issues SAP logon ticket as result of valid OAuth signed request. Widget on OpenSocial container communicates with ticket issuing service to retrieve SAP logon ticket. This service is defined in the corp.sap.pal.widgets.oauth.servlet.OAuthServlet extends HttpServlet class of the OAuthService project. This servlet passes any incoming request to JAAS login stack for validation and upon successful validation expect to get valid SSO ticket in response.

Logon Ticket Responder will be implemented as a servlet in a standard Web Project. For this web application we will configure the OAuth JAAS logon stack, but will make sure that it's not called by the container managed authentication, as we need to invoke programmatically from the servlet with specific callback handlers.

Steps for implementing the SSO ticket responder service:

Creating a Project Structure

For the servlet implementation the following projects must be created:

  • OAuthService - project of the Dynamic Web project type that contains the servlet class;
  • OAuthLoginModulesEAR - project of the Enterprise Application Project type for wrapping the OAuthService project into deployable component.

As the result, the following structure should be available:

Ticket serving servlet in package corp.sap.pal.widgets.oauth.servlet

For OAuthService we need to set additional libraries in the classpath. First, we need our 3rd party libraries JSON.jar to be added to classpath.

Also, set of SAP and J2EE standard libraries is required as well as reference to the OAuthLoginModules project, which contains the RequestCallbackHandler class.

Implementing Ticket Issuing Servlet

Implementing Servlet for NW 7.0

Service code for starting JAAS authentication:

// Do programmatic login to the JAAS stack identified in 
// service initial parameters
String stackName = config.getInitParameter(OAuthServlet.STACK_NAME);

// SAPJ2EECallbackHandler is used to allow OAuth modules to 
// get full access to http request object
LoginContext ctx =
	new LoginContext(
		stackName,
		new SAPJ2EECallbackHandler(req, resp));
ctx.login();

As (and if) JAAS logon stack successfully completes, control is returned to corp.sap.pal.widgets.oauth.servlet.OAuthServlet. At this point servlet has authenticates security context in variable LoginContext ctx. LoginContext variable now can be queried for required information, particularly, SAP SSO logon ticket:

// Retrieve logon ticked from authentication using credentials set in authenticated subject
Set privateCredSet = ctx.getSubject().getPrivateCredentials();
GenericCredential ticketCred = (GenericCredential)privateCredSet.iterator().next();
// value of 'ticket' is set to valid SAP SSO2 logon cookie value, usable by caller for 
// authentication against SAP standard login module {{EvaluateTicketLoginModule}}
String ticket = new String(ticketCred.getCredentialData());

// Create JSON response body with authentication data
Map map = new HashMap();
// set username
map.put("Principal", request.getUserPrincipal().getName());
// set SAP SSO2 ticket
map.put("Ticket", ticket);

// write to response in JSON format
response.setContentType("text/x-json");
JSONObject jo = new JSONObject(map);
jo.write(response.getWriter());

Implementing Servlet for NW 7.1

Service code for starting JAAS authentication:

// Do programmatic login to JAAS stack identified in 
// service initial parameters
String stackName = config.getInitParameter(OAuthServlet.STACK_NAME);

// RequestCallbackHandler is used to allow OAuth modules to 
// get full access to http request object
LoginContext ctx =
	new LoginContext(
		stackName,
		new RequestCallbackHandler(req, resp));
ctx.login();

As (and if) the JAAS logon stack successfully completes, control is returned to corp.sap.pal.widgets.oauth.servlet.OAuthServlet. At this point servlet has authenticates security context in variable LoginContext ctx. LoginContext variable now can be queried for required information, particularly, SAP SSO logon ticket:

// Retrieve logon ticked from authentication using credentials set in authenticated subject
Set privateCredSet = ctx.getSubject().getPrivateCredentials();
GenericCredential ticketCred = (GenericCredential)privateCredSet.iterator().next();
// value of 'ticket' is set to valid SAP SSO2 logon cookie value, usable by caller for 
// authentication against SAP standard login module {{EvaluateTicketLoginModule}}
String ticket = new String(ticketCred.getCredentialData());

// Create JSON response body with authentication data
Map map = new HashMap();
// set username
map.put("Principal", request.getUserPrincipal().getName());
// set SAP SSO2 ticket
map.put("Ticket", ticket);

// write to response in JSON format
response.setContentType("text/x-json");
JSONObject jo = new JSONObject(map);
jo.write(response.getWriter());

Configuration and Deployment

Steps for completing the package creation:

  1. In the OAuthService project, register our servlet:
    • Open web.xml. Add servlet definition and init parameter section as shown in this example. Init parameter points to the servlet themselves.
           <servlet>
              <servlet-name>OAuthServlet</servlet-name>
              <servlet-class>corp.sap.pal.widgets.oauth.servlet.OAuthServlet</servlet-class>
              <init-param>
                  <param-name>jaas_stack_name</param-name>
                  <param-value>sap.com/OAuthServiceEAR*OAuthService</param-value>
              </init-param>
          </servlet>
      
      init parameter jaas_stack_name points to servlet themselves. If other project names are used this parameter must be
      changed accordingly.
    • Define servlet mapping in web.xml file.
         <servlet-mapping>
              <servlet-name>OAuthServlet</servlet-name>
              <url-pattern>/auth</url-pattern>
          </servlet-mapping>
      
  2. Add JAAS login stack configuration for servlet
    • Add following lines in web-j2ee-engine.xml file inside tag <web-j2ee-engine>.
        	<login-module-configuration>
      		<login-module-stack>
      			<login-module>
      				<login-module-name>
      					OAuthValidationModule
      				</login-module-name>
      				<flag>REQUISITE</flag>
      				<options>
      				<!-- holds all the options of the login module -->
      					<option>
      					<!-- holds the name/value pair of an option -->
      						<name>oauth_paramter</name>
      						<value>opensocial_viewer_id</value>
      					</option>
      					<option>
      					<!-- holds the name/value pair of an option -->
      						<name>keystore_name</name>
      						<value>OAuthKeystore</value>
      					</option>
      					<option>
      					<!-- holds the name/value pair of an option -->
      						<name>url_override</name>
      						<value>http://<host>:<port>/OAuthService/auth</value>
      					</option>
      				</options>
      			</login-module>
      			<login-module>
      				<login-module-name>
      					OAuthUserMappingModule
      				</login-module-name>
      				<flag>REQUISITE</flag>
      				<options>
      					<!-- holds all the options of the login module -->
      					<option>
      						<!-- holds the name/value pair of an option -->
      						<name>system_alias</name>
      						<value>OpenSocialAlias</value>
      					</option>
      				</options>
      			</login-module>
      			<login-module>
      				<login-module-name>
      					CreateTicketLoginModule
      				</login-module-name>
      				<flag>SUFFICIENT</flag>
      			</login-module>
      		</login-module-stack>
      		<security-policy-domain>TEST</security-policy-domain>
      	</login-module-configuration>
      
    • Register the corp.sap.pal.widgets.oauth.servlet.OAuthServlet servlet class to URL pattern /auth
  3. If not yet done, add OAuthService project to EAR:
    • Right click the OAuthServiceEAR project.
    • Select Add Modules and the OAuthService module
  4. Create EAR file:
    • Right click the OAuthServiceEAR project.
    • Select Build Application Archive.

As the result, there is the EAR file created in the root of the OAuthServiceEAR project. See How To Configure OAuth Signed Fetch Services and Modules for the application deployment, configuration, and usage details.

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.