SAML, JAAS, & Role-Based Access Control: Part 2

Invoking Web services by attaching SAML tokens to SOAP messages from within Java apps


July 17, 2008
URL:http://www.drdobbs.com/jvm/saml-jaas-role-based-access-control-par/209100671

Frank Teti is a Consulting Technical Manager within Oracle's SOA practice. He can be reached at [email protected].


In Part 1 of this article, I discussed using a Java application as a Web service client and a creative way to secure that client using JAAS and SAML. Part 2 is a consequence of the same application, but I examine how to attach a SAML token to a SOAP message from within a Java application to invoke a Web service that is secured using WS-Security SAML policy file. Here I focus on the mechanism needed to invoke a secure Web service.

I don't discuss configuring SAML on an application server, generating Web service client stubs, annotating a Web service to use SAML as a WS-Security token or configuring a SAML provider/authority. Those types of things are implementation-specific, but important when creating an end-to-end SOA security architecture.

Application Design

Using an IDE or Ant script, it is relatively easy to design and create Web services (and to compile the bindings) that invoke the service from a remote client application. Adding security to the application, on the other hand, is a horse of a different color. However, if security is not seriously considered, then you should face the fact that you are building what I call a "Pet Store" application.

SAML Application Architecture and the Security Workflow

Figure 1, a high-level view of the deployment architecture for the target Java application, depicts the security model workflow. In the model, the Java application makes an HTTP(S) call to the SAML authority inside the firewall using a .NET service that integrates with AFDS (Active Directory Federation Service). The return parameter is a signed, SAML token that is generated based on the user's credentials (i.e., a Kerberos ticket). This token is injected into a SOAP header from within a Java application, which can then invoke a secure Web service that is protected using WS-Security.

[Click image to view at full size]
Figure 1: Java application acquires a SAML token to invoke a secure Web service requests.

Client-side Java Application Objects Roles and Responsibilities

Figure 2 illustrates the objects used for marshaling Web services, including injecting a SAML token into the SOAP message:

[Click image to view at full size]
Figure 2: UML View of Participating SOAP Message Handling Classes

Web service Client Security: Approach and Configuration

Step 1: NT login configuration

The approach uses a JAAS NT login module. Login modules are identified by a name in a configuration file (see Listing One).


GetNTLoginModule  {
com.sun.security.auth.module.NTLoginModule required; 
};

Listing One: jaas.conf configuration file

Most of these modules expect to be run interactively by a user either from an application or on the command line. The argument -D java.security.auth.login.config=jaas.conf is required on the JVM. Essentially, this authenticates a user (who has already logged into NT) within a Java application. The LoginContext object constructor instantiates itself as an NT login (see Listing Two).


lc = new LoginContext("GetNTLoginModule");
lc.login();

Listing Two: JAAS login within the Java application

Step 2: Token acquisition from a SAML provider

For Web services security, a remote call must be made to a SAML provider in order to get the signed, XML token. Essentially, Listing Three should be viewed as an interface method that needs to be implemented, based on the SAML provider in your architecture.

After the token is acquired, it should be stored within a String object. A cached assertion can be reused for subsequent Web service calls during the lifetime of the client session. However, for long running clients, the SAML assertion may expire, which will result in a server-side SOAP fault.

The Java application should be designed to specifically catch an expired assertion fault and force an automatic acquisition of a new token. In the vast schema of things, expiration dates could be set such that they may not expire to well into the future. In effect, though, this type of policy would represent weak management and a breach of best practices.

However, technically speaking, the SOAPMessage object provides behavior for persisting a SOAP message to a local file (see Listing Six) and the application could be designed to be reentrant from a security standpoint. Needless to be said, just because something is viable doesn't mean it is a good thing to do.


String assertion = asserter.getAssertion();

Listing Three: Storing a SAML token as a String object

While the asserter behavior is implementation-specific, the XML response needs to be well-formed XML assertion (see Listing Four). Note that this assertion is specified to use groups, which will allow the application server to secure the Web service by role.

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> 
<soapenv:Header> 
<saml:Assertion AssertionID="_a1ddddfcd0e6ca80f093d9562ce26b39" ID="_a1ddddfcd0e6ca80f093d9562ce26b39" IssueInstant="2008-03-28T17:54:59.59Z" Issuer="http://xyz.abc.com" MajorVersion="1" MinorVersion="1" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"> 
 ...
<saml:Attribute AttributeName="Groups" AttributeNamespace="urn:bea:security:saml:groups"> 
<saml:AttributeValue>Customer_Accounting</saml:AttributeValue> 
</saml:Attribute> 
<saml:Attribute AttributeName="Groups" AttributeNamespace="urn:bea:security:saml:groups"> 
<saml:AttributeValue>Customer_Service</saml:AttributeValue> 
</saml:Attribute> 
 ...
</saml:Assertion> 
</soapenv:Header> 
 ...
</soapenv:Envelope>
Listing Four: SAML Assertion (Token)

Step 3: Security preprocessing steps before invoking the Web service

The "secure" operation method is a method on the Java application which is responsible for:

It is important to point out that for a production applications, handler implementation-details should be encapsulated in a set of objects outside of the programmer space.

An explanation of the processing steps is embedded as comments in Listing Five.

private void secureOperation( Service_Impl service, Hello port,  Stub helloStub, String assertion) {

    try {
/* Get the handler registry from the service. */

HandlerRegistry hr = service.getHandlerRegistry();

/* Create hash map (i.e. config) that will be used to store initialization parameters for client-side handler. */

HashMap<String,String> config = new HashMap<String,String*gt;();

/* The assertion is retrieved by the application at start up and stored as a class or static object. */

config.put("SAML_ASSERTION", assertion);

/* Create HandlerInfo object, which will store the config object. */

HandlerInfo hi = new HandlerInfo();
hi.setHandlerConfig(config);

/* Set the SAMLAuthenticationHandler.class into the HandlerInfo object. */
 
hi.setHandlerClass(SAMLAuthenticationHandler.class);

/* Create an ArrayList and add the HandlerInfo object to it. */

ArrayList<HandlerInfo> handlerChain = new ArrayList<HandlerInfo>();
handlerChain.add(hi);

/* On the HandlerRegistry set the Target namespace, SOAP service port name 
and Arraylist that contains the config and the AuthenticationHandler. */

hr.setHandlerChain(new QName(_properties.getProperty("targetNamespace"),
_properties.getProperty("portName")), 
handlerChain);

/* Invoke the Web service which now contains a SAML assertion in the header of the message. */
String result = port.secureServiceMethod ());
} catch (Throwable throwable) { 
 ...
}}
Listing Five: Secure Web service method

Step 4: Injecting the SAML token into the SOAP header

In real-time, the SAMLAuthenticationHandler object has completed processing before the Web service is invoked in Listing Five. The SAMLAuthenticationHandler is responsible for setting the SAML token into the SOAP header.

An explanation of the processing steps is embedded as comments in Listing Six.

public class SAMLAuthenticationHandler extends GenericHandler {

	private String assertion = null;
	private static final String NS_WSSE = 
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";	
	private SOAPFactory sf = null;
	private QName[] headers;
	
	public SAMLAuthenticationHandler() throws SOAPException {
		super();
		sf = SOAPFactory.newInstance();
}	

	public void init(HandlerInfo handlerInfo) {		
		super.init(handlerInfo);
		Map config = handlerInfo.getHandlerConfig();

		/* Get the assertion that is stored in the Config object */

		assertionString = (String) config.get("SAML_ASSERTION");		
	}	

	public QName[] getHeaders() {
		
		return headers;
	}
	
	public boolean handleRequest(MessageContext context) {
		
	    try {
/* Bind SAML token into security head by instantiating a message context */
 	
		SOAPMessageContext messageContext = (SOAPMessageContext) context;
		
		/* Instantiating a message context */
	
SOAPMessage message = messageContext.getMessage();	
		
		/* Create an input stream, based on the assertion */
	
		InputStream bin = new ByteArrayInputStream(assertionString.getBytes("UTF-8"));
		  		
		/* Get a SOAP header */

		SOAPHeader header = message.getSOAPPart().getEnvelope().getHeader();
		
		/* Add a schema statement to the header */

		SOAPElement securityHeaderElement = 
header.addChildElement(sf.createName("Security", "wsse", NS_WSSE));

		/* Add a namespace statement to the header */ 
		securityHeaderElement.addNamespaceDeclaration("wsse", NS_WSSE);	

/* Get a document element form the assertion */

Node assertionNode = DocumentBuilderFactory.newInstance().newDocumentBuilder().
parse(new ByteArrayInputStream(assertion.getBytes())).getDocumentElement();
			
		bin.close();	
			
		/* Add the SAML assertion to the header */

securityHeaderElement.addChildElement(convert(assertionNode));
            		            
            		/* Optionally, for testing purposes write message to file for testing purposes */

            		message.writeTo(new FileOutputStream("XMLRequest1.xml")); 		

	    } catch (Exception e1) {
		...		
	   }
		
		return true;
	}
	
	/* The methods below convert and copy are SOAP helper processing methods */

	private SOAPElement convert(Node element) throws SOAPException {
	
    SOAPElement soapElement  = sf.createElement(element.getNodeName(), 
element.getPrefix(), element.getNamespaceURI());
	    copy(element, soapElement);
	    return soapElement;
	
}
	
	private void copy(Node source, SOAPElement target) throws DOMException, SOAPException {
		
	    for (int j = 0; j < source.getAttributes().getLength(); j++) {
		Node node = source.getAttributes().item(j);
			
		String name = node.getNodeName();
		if (name.startsWith("xmlns")) {
		    int index = name.indexOf(":");
		    if (index < 0) {//default namespace
			target.addNamespaceDeclaration("", node.getTextContent());
		    } else {
			target.addNamespaceDeclaration(name.substring
("xmlns:".length()), node.getTextContent());
		    }
		} else {
		    target.addAttribute(sf.createName(name, 
node.getPrefix(), node.getNamespaceURI()), node.getTextContent());
		}
                    }		
			    for (int i = 0; i < source.getChildNodes().getLength(); i++) {
		Node node = source.getChildNodes().item(i);
			
		if (Node.ELEMENT_NODE == node.getNodeType()) {
		//copy element
		    SOAPElement targetElement = target.addChildElement(
                                            node.getNodeName(), node.getPrefix(), node.getNamespaceURI());
				
				//copy subelements
				copy(node, targetElement);				
		} else if (Node.TEXT_NODE == node.getNodeType()) {
				//copy text nodes
				target.addTextNode(node.getTextContent());
		}			
	   }
              }
}
Listing Six: SAML authentication handler

Implications

In practice, most of the code in this article should be abstracted away from Java application programmers. Essentially, information contained in SOAP objects is opaque. The size and shape of opaque objects should not be directly accessible to the user. Opaque objects should be accessed via handles, which exist in user space.

Even issues such as exception handling for expired tokens are system programmer's responsibility, not application programmers. As is the case with all security-related programming, the objective is to keep the application programmer productive, while enforcing the organizations security polices.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.