Core Security Patterns: Securing the Identity--Design Strategies and Best Practices
- Identity Management Security Patterns
- Best Practices and Pitfalls
- References
Topics in This Chapter
- Identity Management Security Patterns
- Best Practices and Pitfalls
In Chapter 7, “Identity Management Standards and Technologies,” we introduced the identity management and the relevant security standards, such as SAML, Liberty, and XACML. SAML is an XML protocol for representing authentication and authorization assertions and is used for single sign-on and global logout. Liberty reuses the security assertion framework from SAML and extends it with different identity profiles and framework. XACML provides a versatile policy management framework for administering access control rules and managing security policies. These security standards are important because they allow different security vendor products to interoperate with each other. Architects and developers are now able to work with different security infrastructures without rewriting most of the security components.
In a heterogeneous application infrastructure environment, each application system may have different user authentication mechanisms and customized authorization schemes. Enabling unified access with single sign-on and exit with global logout is a complex process. To handle this, architects and developers may need to build custom mechanisms or ensure that the current sign-on mechanisms support open standards such as SAML and Liberty. In either case, there is much similar processing logic and code in the Identity tier that can be refactored for reuse. You may also want to extract some commonly used security processing into helper classes instead of embedding the security processing logic into each of the authentication and authorization functions. If different versions of security packages are used simultaneously (for example, due to support for legacy systems), it would be useful to build an abstraction layer (using façde or delegate patterns) so that the identity management functionality can support multiple versions with backward compatibility. In these scenarios, adopting security patterns would be useful in addressing these requirements in the Identity tier.
In identity management, security patterns can provide a common design framework, unified SSO, and global-logout mechanisms for use with heterogeneous applications. They can also reduce significant design and development effort, because they simplify complexity while they capture many security best practices. Using abstraction, these security patterns can shield off custom-built or specific-vendor APIs, and this can reduce the impact of vendor lock-in if customers want to switch to another vendor product. Additionally, because security standards are still evolving, the abstraction layer in the security pattern can help mitigate the risk of migrating to new security standards.
There are many security vendor products and open source toolkits available in the market today that support SAML, Liberty, and XACML. Some security vendor products provide administrator-friendly agents or adapters so that you do not need to customize their applications by adding SAML protocols or Liberty identity profiles to their software program code. This is particularly useful for those who have a large amount of packaged applications purchased from vendors. Nevertheless, there are many of them who have home-grown applications that need to work with standards-based identity management architecture. This mandates a reusable identity management framework and security patterns to resolve the recurring problems and complexities.
Identity Management Security Patterns
Assertion Builder Pattern
Problem
You need a structured and consistent approach to gathering security information (for example, SAML assertions) about the authentication action performed on a subject, attribute information about the subject, or an authorization request from a trusted service provider.
Security assertions are authentication and authorization-related information exchanged between trusted service providers and requesters, and are used as a common mechanism for enabling applications to support SSO without requiring the client to login multiple times. To enable a trusted environment, we need to address the requirements of SSO with heterogeneous applications, discrete authentication schemes, authorization policies, and other related attributes in use. This requires having a generic mechanism for constructing and processing SAML-based assertions.
Forces
- You want to avoid duplicate program logic for building authentication assertion, authorization decision assertion, and attribute statements.
- You need to apply common processing logic to similar security assertion statements.
- You need a helper class to extract similar processing logic to build SAML assertion statements instead of embedding them into the authentication and authorization processes.
- You want the flexibility to support client requests from a servlet, EJB client, or a SOAP client.
Solution
Use an Assertion Builder to abstract similar processing control logic in order to create SAML assertion statements.
The Assertion Builder pattern encapsulates the processing control logic in order to create SAML authentication statements, authorization decision statements, and attribute statements as a service. Each assertion statement generation shares similar program logic of creating the SAML header (for example, schema version) and instantiating the assertion type, conditions, and subject statement information. The common program logic can also be used to avoid locking in with a specific product implementation. By exposing the Assertion Builder as a service, developers can also access SAML assertion statement creation using SOAP protocol binding without creating separate protocol handling routines.
Under a single sign-on environment (refer to Figure 12–3), a client authenticates with a single sign-on service provider (also known as the source site) and later requests access to a resource from a destination site. Upon successful authentication, the source site is able to redirect the client request to the destination site, assuming that the source site has a sophisticated security engine that determines the client is allowed to access the destination site. Then, the destination site will issue a SAML request to ask for an authentication assertion from the source site. The Assertion Builder will be used to assemble sign-on information and user credentials to generate SAML assertion statements. This is applicable for both the source site (processing SAML responses) and the destination site (processing SAML requests). The destination site will then respond to the client for resource access. Subsequently, the destination site will handle authorization decisions and attribute statements to determine what access level is allowed for the client request.
Figure 12–1 depicts a high-level architecture diagram of the Assertion Builder pattern. In a typical application scenario, developers can design an Assertion Builder to provide the service of generating SAML authentication statements, SAML authorization decision statements, and SAML attribute statements. The Assertion Builder creates a system context (Assertion Context) and produces a SAML assertion statement (Assertion), which can be an authentication statement, an authorization decision statement, or an attribute statement. An EJB client can perform a JNDI service look-up of the SAML Assertion Builder service and invoke the preliminary utilities to assemble the SAML headers. After that, it invokes the relevant statement generation function, such as authentication statement. Similarly, a servlet can perform service invocation by acting as an EJB client. For a SOAP client, the Assertion Builder service bean needs to be exposed as a WSDL. Upon service invocation, the Assertion Builder utilities will marshal and unmarshal the SOAP envelope when the protocol binding is set to SOAP.
Structure
Figure 12–2 shows a class diagram for Assertion Builder service. The core Assertion Builder service consists of two important classes: AssertionContext and Assertion. The AssertionContext class defines the public interfaces for managing the system context when creating SAML assertion statements, SAML assertion types, and the protocol binding. If the SAML binding is set to be SOAP over HTTP, then the Assertion Builder service needs to wrap the SAML artifacts with a SOAP envelope instead of the HTTP header. It has a corresponding implementation class called AssertionContextImpl.
Figure 12–1 Assertion Builder logical architecture
Figure 12–2 Assertion Builder class diagram
The Assertion class refers to the SAML assertion statement object. It contains basic elements of subject information (such as subject’s IP address, subject’s DNS address), source Web site, and destination Web site for the creation of SAML assertion statements. There is a corresponding data class called Subject, which refers to the principal for the security authentication or authorization. Each Assertion contains a Subject element. The Assertion class is also extended into AuthenticationStatement, AuthorizationDecisionStatement, and AttributeStatement. Each of these three assertion statement classes is responsible for creating SAML assertion statements, respectively, according to the SAML 2.0 specification. Attribute is a data class that encapsulates the distinctive characteristics of a subject and denotes the attributes in a SAML attribute statement.
Participants and Responsibilities
Figure 12–3 depicts a use case scenario where a client is requesting resource access from a destination site via the source site that acts as a single sign-on service provider. Client refers to a Web browser that initiates the resource access request. SourceSite denotes the single sign-on service provider that manages security information about which resources can be accessible in other affiliated sites, including DestinationSite. DestinationSite denotes the target site with resources that Client intends to access.
Figure 12–3 Assertion Builder sequence diagram
This is a scenario for a Web browser interacting with the source and destination sites with single sign-on (i.e., browser profile), and it is not applicable to a server-to-server single sign-on scenario. The Assertion Builder pattern is implemented for Steps 3 through 14. The other steps are provided here to provide a better context only.
- Client accesses resources provided by the service provider (SourceSite).
- SourceSite verifies if Client is authenticated already.
- SourceSite creates an instance of AssertionBuilder. In this scenario, the instance is for creating a SAML authentication assertion request.
- DestinationSite also creates an instance of AssertionBuilder. This is for creating a SAML authentication assertion response.
- AssertionBuilder retrieves SAML protocol binding (for example, SOAP binding) for the interaction with SourceSite or DestinationSite.
- SourceSite redirects the resource access to the destination site DestinationSite via URL redirection.
- Client accesses the artifact receiver URL.
- AssertionBuilder assembles information to build the SAML header and tokenizes the user credentials (i.e., creates any security token from the user credentials) to facilitate the interaction with DestinationSite.
- AssertionBuilder creates the relevant protocol binding. For example, if this is a SOAP binding, then AssertionBuilder creates the SOAP envelope and uses the SOAP binding protocol.
- AssertionBuilder creates a SAML assertion statement (for example, SAML authentication assertion request) and sends it to DestinationSite.
- DestinationSite issues a SAML request for any authentication assertion statement to SourceSite.
- AssertionBuilder assembles information to build the SAML header and tokenizes the user credentials to facilitate interaction with SourceSite.
- AssertionBuilder creates the relevant protocol binding. For example, if this is a SOAP binding, then AssertionBuilder creates the SOAP envelope and uses the SOAP binding protocol.
- AssertionBuilder creates a SAML assertion statement (for example, SAML authentication assertion response) and sends it to SourceSite.
- SourceSite issues a SAML response to DestinationSite.
- DestinationSite responds to user response for resources at the destination site.
Strategies
Protocol Binding Strategy
It is possible that the same client may be using a mixture of SOAP over HTTP and SOAP over HTTPS SAML requests under different use case scenarios. It is important to be flexible about the protocol binding so that different protocols are supported. To accommodate such flexibility, developers can use a custom protocol binding look-up function to determine which SAML protocol binding is used for the SAML request.
Time Checking Strategy
Developers can add extra security control by adding timestamp comparison control for processing SAML responses in order to address the security risks of replay, message insertion, or message deletion. They can also compare the AuthenticationInstant timestamp in the SAML request and the SAML response. Typically, the timestamp in the SAML response is slightly behind the SAML request time. Another extension is to define a timeout strategy for the authentication timestamp. For example, a strategy might be that the destination site will not allow access for a client request if the authentication timestamp is over 120 minutes ago.
Audit Control Strategy
Although the subject IP and DNS addresses are optional in the current SAML 1.1 or 2.0 specifications, it is always a good design strategy to capture them for audit control purposes. For example, some hackers may be able to replay SAML assertion statements within the customer LAN. By tracking the subject IP and DNS addresses, security architects and developers are able to quickly detect any SAML assertion statements with unusual IP and DNS addresses.
Using Assertion Builder Pattern in Single Sign-on
The Assertion Builder pattern should be used in conjunction with the Single Sign-on Delegator (refer to next section for details). The Single Sign-on Delegator shields off the design complexity of remote communication with different assertion statement creation services and utilities of the Assertion Builder, as well as other single sign-on services such as Java Connector for legacy systems. This is particularly useful when these single sign-on services are provided in a variety of technologies, such as EJBs, servlets, Java Beans, and custom applications.
Consequences
By employing the Assertion Builder pattern, developers will be able to benefit in the following ways:
- Addresses broken authentication flaw. The Assertion Builder pattern can be used to build a helper class that creates and sends SAML authentication statements between trusted service providers. The SAML authentication statement denotes security information regarding authentication data about the subject. This helps to safeguard the authentication mechanisms from a potential broken authentication flaw.
- Addresses broken access control risk. The Assertion Builder pattern can be used to create SAML authorization decision and attribute statements. The SAML authorization decision statement denotes a critical decision about granting or denying resource access for a subject. This helps to safeguard the access control mechanisms from potential broken access control flaws.
- Enables transparency by encapsulating the assertion statements. The Assertion Builder pattern encapsulates three different assertion statement creation functionalities with similar processing logic. It is easier to maintain and use. In addition, architects and developers do not need to embed the processing logic of building SAML assertion statements in the business processing logic.
- Reduces the complexity of integration. The Assertion Builder pattern allows a flexible service invocation from a variety of clients, including servlets, EJB clients, and SOAP clients. It reduces the integration effort with different platforms.
Sample Code
Example 12–1 shows a sample code excerpt for creating an Assertion Builder for SAML assertion requests. The example creates a SAML authentication statement, a SAML authorization decision statement, and a SAML attribute statement. It defines the assertion type (using the setAssertionType method), initializes the assertion statement object, and sets relevant attributes (for example, setAuthenticationMethod for an authentication statement) for the corresponding assertion statement object. Then it uses the createAssertionStatement method to generate the SAML assertion statement in a document node object. It checks its validity upon completion of the SAML statement creation. It also retrieves the service configurations and protocol bindings (for example, SOAP over HTTP binding) before building SAML assertion statements.
Example 12–1 Sample Assertion Builder implementation
package com.csp.identity; import java.util.ArrayList; import java.util.Collection; public class AssertionBuilder { // common variables and constants protected com.csp.identity.AssertionContextImpl assertionFactory; protected com.csp.identity.Subject subject; protected static final String authMethod = "urn:oasis:names:tc:SAML:1.0:am:password"; protected static final String sourceSite = "http://www.coresecuritypattern.com"; protected static final String destinationSite = "http://www.raylai.com"; protected static final String subjectDNS = "dns.coresecuritypattern.com"; protected static final String subjectIP = "168.192.10.1"; protected static final String subjectName = "Maryjo Parker"; protected static final String subjectQualifiedName = "cn=Maryjo, cn=Parker, ou=authors, o=coresecurity, o=com"; // authentication assertion specific protected com.csp.identity.AuthenticationStatement authenticationStatement; protected org.w3c.dom.Document authAssertionDOM; // authorization decision assertion specific protected com.csp.identity.AuthorizationDecisionStatement authzDecisionStatement; protected static final String decision = "someDecision"; protected static final String resource = "someResource"; protected java.util.Collection actions = new ArrayList(); protected java.util.Collection evidence = new ArrayList(); protected org.w3c.dom.Document authzDecisionAssertionDOM; // attribute assertion specific protected com.csp.identity.AttributeStatement attributeStatement; protected com.csp.identity.Attribute attribute; protected Collection attributeCollection = new ArrayList();; protected org.w3c.dom.Document attributeStatementDOM; /** Constructor - Creates a new instance of AssertionBuilder */ public AssertionBuilder() { // common assertionFactory = new com.csp.identity.AssertionContextImpl(); subject = new com.csp.identity.Subject(); subject.setSubjectName(subjectName); subject.setSubjectNameQualifier(subjectQualifiedName); assertionFactory.setAssertionType (com.csp.identity.AuthenticationStatement .ASSERTION_TYPE); // ====create authentication statement============= // create authentication assertion object attribute authenticationStatement = new com.csp.identity.AuthenticationStatement(); assertionFactory.setAuthenticationMethod(authMethod); authenticationStatement.setSourceSite(sourceSite); authenticationStatement .setDestinationSite(destinationSite); authenticationStatement.setSubjectDNS(subjectDNS); authenticationStatement.setSubjectIP(subjectIP); authenticationStatement.setSubject(subject); // create authentication statement System.out.println("**Create authentication statement **"); authAssertionDOM = assertionFactory.createAssertionStatement ((com.csp.identity.AuthenticationStatement) authenticationStatement); //===end of create authentication statement ======== //====create authorization decision statement======= // create authorization decision assertion // object attribute authzDecisionStatement = new com.csp.identity.AuthorizationDecisionStatement(); authzDecisionStatement.setSourceSite(sourceSite); authzDecisionStatement .setDestinationSite(destinationSite); authzDecisionStatement.setSubjectDNS(subjectDNS); authzDecisionStatement.setSubjectIP(subjectIP); authzDecisionStatement.setResource(resource); authzDecisionStatement.setDecision(decision); authzDecisionStatement.setSubject(subject); assertionFactory.setAssertionType (com.csp.identity.AuthorizationDecisionStatement .ASSERTION_TYPE); // Prepare evidence this.evidence.add("Evidence1"); this.evidence.add("Evidence2"); this.evidence.add("Evidence3"); authzDecisionStatement.setEvidence(evidence); // Prepare action this.actions.add("Action1"); this.actions.add("Action2"); this.actions.add("Action3"); authzDecisionStatement.setActions(actions); // create authorization decision statement System.out.println("**Create authorization decision statement **"); authzDecisionAssertionDOM = assertionFactory.createAssertionStatement ((com.csp.identity.AuthorizationDecisionStatement) authzDecisionStatement); // ===end of create authorization statement ====== // =====create attribute statement ============= // create attribute assertion object attribute attributeStatement = new com.csp.identity.AttributeStatement(); attributeStatement.setSourceSite(sourceSite); attributeStatement.setDestinationSite(destinationSite); attributeStatement.setSubjectDNS(subjectDNS); attributeStatement.setSubjectIP(subjectIP); attributeStatement.setSubject(subject); assertionFactory.setAssertionType (com.csp.identity.AttributeStatement.ASSERTION_TYPE); // Prepare attribute attribute = new com.csp.identity.Attribute(); this.attributeCollection.add("Attribute1"); this.attributeCollection.add("Attribute2"); this.attributeCollection.add("Attribute3"); this.attribute.setAttribute(attributeCollection); attributeStatement.addAttribute(attribute); // create attribute statement System.out.println("**Create attribute statement **"); attributeStatementDOM = assertionFactory.createAssertionStatement ((com.csp.identity.AttributeStatement) attributeStatement); // ===end of create attribute statement === } public static void main(String[] args) { new AssertionBuilder(); } }
Example 12–2 shows how an authentication statement is implemented. An authentication statement extends the object Assertion, which is an abstraction of SAML assertion statements (including the SAML authentication statement, authorization decision statement, and attribute statement). This authentication statement is intended to implement how a SAML authentication assertion is created. The previous createAuthenticationStatement method in the last section will invoke the create method from the AuthenticationStatement class in order to create a SAML authentication statement. The create method can be implemented using custom SAML APIs, provided by a SAML implementation offered by open source or commercial vendor solution. In this example, the create method uses a constructor from the OpenSAML library to create a SAML authentication statement and checks for the validity of the SAML assertion statement.
Example 12–2 Sample AuthenticationStatement code
package com.csp.identity; import java.util.Date; import org.opensaml.*; public class AuthenticationStatement extends com.csp.identity.Assertion { static final String ASSERTION_TYPE = "AUTHENTICATION"; protected com.csp.identity.AuthenticationStatement authStateFactory; /** Constructor - Creates a new instance of AuthenticationStatement * */ public AuthenticationStatement() { } /** * Get instance of the existing authentication * assertion statement * If instance does not exist, create one * * @return AuthenticationStatement instance of * Authentication statement */ public com.csp.identity.AuthenticationStatement getInstance() { if (authStateFactory == null) { authStateFactory = new AuthenticationStatement(); if (authStateFactory == null) System.out.println("WARNING – authStat is null"); } return this.authStateFactory; } /** * Create SAML authentication assertion statement * **/ public void create() { // This example uses OpenSAML 1.0 // but you can use your custom SAML APIs or vendor APIs org.opensaml.SAMLSubject samlSubject; java.util.Date authInstant = new Date(); String samlSubjectIP = this.getSubjectIP(); String samlSubjectDNS = this.getSubjectDNS(); org.opensaml.SAMLNameIdentifier samlNameIdentifier; try { // Create SAML Subject object using OpenSAML 1.0 samlNameIdentifier = new org.opensaml.SAMLNameIdentifier (this.subject.getSubjectName(), this.subject.getSubjectNameQualifier(),""); samlSubject = new org.opensaml.SAMLSubject (samlNameIdentifier, null, null, null); // Create SAML authentication statement // using OpenSAML 1.0 org.opensaml.SAMLAuthenticationStatement samlAuthStat = new org.opensaml.SAMLAuthenticationStatement (samlSubject, authInstant, samlSubjectIP, samlSubjectDNS, null); samlAuthStat.checkValidity(); System.out.println("DEBUG - The current SAML authentication statement is valid!"); } catch (org.opensaml.SAMLException se) { System.out.println("ERROR - Invalid SAML authentication assertion statement"); se.printStackTrace(); } } }
Example 12–3 shows an example of creating a system context for the Assertion Builder pattern, which stores the service configuration and protocol binding information for creating and exchange SAML assertion statements. The AssertionContextImpl class is an implementation of the public interfaces defined in the AssertionContext class. This allows better flexibility in adding extensions or making program changes in the future.
Example 12–3 Sample AssertionContext implementation
package com.csp.identity; public class AssertionContextImpl implements com.csp.identity.AssertionContext { protected String authMethod; protected String assertionType; protected com.csp.identity.AuthenticationStatement authStatement; protected com.csp.identity.AuthorizationDecisionStatement authzDecisionStatement; protected com.csp.identity.AttributeStatement attributeStatement; protected org.w3c.dom.Document domTree; /** Constructor - Creates a new instance of AssertionContextImpl */ public AssertionContextImpl() { } /** set assertion type * @param String assertion type, for example authentication, * attribute **/ public void setAssertionType(String assertionType) { this.assertionType = assertionType; } /** create SSO token * * @param Object security token **/ public void createSSOToken(Object securityToken) { ... } /** check for valid SAML statement * * @return boolean true/false **/ public boolean isValidStatement() { // to be implemented return false; } /** set authentication method * * @param String authentication method **/ public void setAuthenticationMethod(String authMethod) { this.authMethod = authMethod; } /** get authentication method * * @return String authentication method **/ public String getAuthenticationMethod() { return this.authMethod; } /** create SAML assertion statement * * Note - the @return has not been implemented. * * @return org.w3c.dom.Document xml document **/ public org.w3c.dom.Document createAssertionStatement (Object assertObject) { System.out.println("DEBUG - Create SAML assertion in XML doc"); if (this.assertionType.equals (com.csp.identity.AuthenticationStatement .ASSERTION_TYPE)) { // create SAML authentication statement // using OpenSAML 1.0 authStatement = (com.csp.identity.AuthenticationStatement) assertObject; authStatement.create(); } else if (this.assertionType.equals (com.csp.identity.AuthorizationDecisionStatement .ASSERTION_TYPE)) { // create SAML authorization decision // statement using // OpenSAML 1.0 authzDecisionStatement = (com.csp.identity.AuthorizationDecisionStatement) assertObject; authzDecisionStatement.create(); } else if (this.assertionType.equals( com.csp.identity.AttributeStatement.ASSERTION_TYPE)) { // create SAML authorization decision statement // using // OpenSAML 1.0 attributeStatement = (com.csp.identity.AttributeStatement) assertObject; attributeStatement.create(); } return null; } /** get SAML assertion statement * * @return org.w3c.dom.Document xml document **/ public org.w3c.dom.Document getAssertionStatement() { // to be implemented return null; } /** remove assertion statement * **/ public void removeAssertionStatement() { // to be implemented } /** create assertion reply * * @return org.w3c.dom.Document xml document **/ public org.w3c.dom.Document createAssertionReply(Object assertionRequest) { ... return null; } /** get assertion reply * * @return org.w3c.dom.Document xml document **/ public org.w3c.dom.Document getAssertionReply() { ... return null; } /** remove assertion reply * **/ public void removeAssertionReply() { ... } /** set protocol binding * * @param String protocol binding **/ public void setProtocolBinding (String protocolBinding){ ... } /** get protocol binding * * @return String protocol binding **/ public String getProtocolBinding() { ... return null; } }
Security Factors and Risks
The Assertion Builder pattern is a reusable design that simplifies the creation of SAML assertion statements and can cater to either the synchronous or asynchronous mode of service invocation. The following discusses the security factors associated with the Assertion Builder pattern and the potential risk mitigation.
- Configuration issues. Assertion Builder relies on strong authentication by the identity provider. Improper configuration of authentication mechanisms and flawed credential management that compromises application authentication through password change will still lead to broken authentication.
- Identity theft. If a user identity is stolen by attackers who uses the user ID and password for proper authentication, Assertion Builder will not be able to address such a security risk. Use of XML Signature will help verifying the signer and also ensure the message is integral and tamper-proof during transit.
- Confidentiality. Using the HTTPS protocol to protect the client-to-server session is a stronger means of supporting confidentiality, because no unauthorized user can snoop the SAML assertion statements from the wire.
- XML digital signature. Using XML digital signature to the SAML assertion statements assures that no one can modify or tamper with the message content.
- Reliability. SAML assertion statements can be bound to a reliable data transport mechanism such as SOAP over JMS. This ensures that the recipient (either the source site or the destination site) can reliably get the SAML request or response.
Reality Check
- Should we build assertion builder code from scratch? There are a few security vendor products that have out-of-the-box SAML assertion statement builder capability. In this case, architects and developers may either directly invoke the SAML assertion builder function or abstract them under the Assertion Builder pattern.
- Capturing IP address. Although the SAML assertion statement allows capturing the source IP address, it is rather difficult to capture the real IP address in real life because real IP addresses can be translated into another virtual IP address or hidden from proxies. However, it is still a good practice to capture the IP address for verifying the origin host for authenticity, troubleshooting and other auditing purposes.
- Dependency on authentication infrastructure. It is plausible to enable single sign-on security by using SAML assertions alone. However, SAML assertions depend on an existing authentication infrastructure.
- Migration from SAML 1.1 to SAML 2.0. There are some deprecated items and changes in SAML 2.0. The SAML specifications do not provide guidance on how to migrate from SAML 1.1 to SAML 2.0, or how to make them compatible between trading partners running different SAML versions. Thus, it is important to cater to service versioning of SAML messages and to migrate the messaging infrastructure to SAML 2.0.
Related Patterns
- Single Sign-on Delegator. Single Sign-on Delegator provides a delegate design approach to connect to remote security services and enables single sign-on within the same security domain or across multiple security domains. It is a good fit to use Assertion Builder in conjunction with Single Sign-on Delegator.
Single Sign-on (SSO) Delegator Pattern
Problem
You want to hide the complexity of interacting directly with heterogeneous service invocation methods or programming models of remote identity management or single sign-on service components.
In a heterogeneous security environment, you may need to use multiple vendor products to build their custom identity management functionality, such as account provisioning and authentication. Each vendor product may require different service invocation methods or programming models. If developers design the client to interact directly with remote identity management or single sign-on service interfaces, they probably need to add vendor-specific or fine-grained business logic in the client. This usually results in deploying a heavy client footprint or building rich clients that are loaded with complex security-specific business logic. Thus, such tight-coupling of the client directly with vendor-specific business logic creates many limitations for scalability in client-side performance, network connectivity, server-side caching, and support of a large number of simultaneous connections. In addition, you need to explicitly handle different types of network or system exceptions in the individual vendor-specific business logic while invoking the remote security services directly.
One related problem is software code maintenance and release control issues. If any identity management service interface changes, for example, due to a change in security standards or API specifications, developers have to maintain any corresponding client-side code changes. The client-side code also needs to be redeployed. This is quite a considerable software release control and maintenance issue because the tight-coupling architecture model is not flexible enough to accommodate software code changes.
Another problem is the lack of a flexible programming model for adding or managing new identity management functionalities if the existing vendor-specific APIs currently do not support them. For example, if the current security application architecture does not support global logout, it defeats the purpose of Single Sign-on in an integration environment, which may create authentication issues and session hijacking risks. At the worst, developers are required to rewrite the security application architecture every time they integrate a new application. Developers may also need to add newer functionalities in order to achieve Single Sign-on.
Forces
- You want to minimize the coupling between the clients and the remote identity management services for better scalability or for easier software maintenance.
- You want to streamline adding or removing identity management or single sign-on security service components (such as global logout), without reengineering the client or back-end application architecture.
- You want to hide the details of handling heterogeneous service invocation bindings (for example, EJB and asynchronous messaging) and service configuration of multiple security service components (for example, identity server and directory server) from the clients.
- You want to translate network exceptions caused by accessing different identity management service components into the application or user exceptions.
Solution
Use a Single Sign-on Delegator to encapsulate access to identity management and single sign-on functionalities, allowing independent evolution of loosely coupled identity management services while providing system availability.
A Single Sign-on Delegator resides in the middle tier between the clients and the identity management service components. It delegates the service request to the remote service components. It de-couples the physical security service interfaces and hides the details of service invocation, retrieval of security configuration, or credential token processing from the client. In other words, the client does not interact directly with the identity management service interfaces. The Single Sign-on Delegator in turn prepares for Single Sign-on, configures the security session, looks up the physical security service interfaces, invokes appropriate security service interfaces, and performs global logout at the end. Such loosely coupled application architecture minimizes the change impact to the client even though the remote security service interfaces require software upgrade or business logic changes.
A Business Delegate pattern would not be appropriate because it simply delegates the service request to the corresponding remote business components. It does not cater to configuring the security session or delegating to the remote security service components using the appropriate security protocols and bindings. Alternatively, developers can craft their own program construct to access remote service components. Using a design pattern approach to refactor similar security configuration (or preambles) for multiple remote security services into a single and reusable framework will enable higher reusability. The Single Sign-on Delegator pattern refactors similar security session processing logic and security configuration, and increases reusability.
To implement the Single Sign-on Delegator, you apply the delegator pattern that shields off the complexity of invoking service requests of building SAML assertions, processing credential tokens, performing global logout, initiating security service provisioning requests, and any custom identity management functions from heterogeneous vendor product APIs and programming models. They can create a unique service ID for each remote security service, create a service handler for each service interface, and then invoke the target security service. Under this delegator framework, it is easy to use the SAML protocol to perform single sign-on across remote security services. Similarly, it is also flexible enough to implement global logout by sending logout requests to each remote service because the delegator holds all unique service IDs and the relevant service handlers.
You can also use the Single Sign-on Delegator in conjunction with J2EE Connector Architecture. Single Sign-on Delegator can populate the security token or security context to legacy system environments, including ERP systems or EIS. If ERP systems have their own connectors or adapters, Single Sign-on Delegator can also exchange security tokens by encapsulating their connector APIs.
One major benefit of using the Single Sign-on Delegator is the convenience of encapsulating access to vendor-specific identity management APIs. Doing so shields the business components from changes in the underlying security vendor product implementation.
Structure
Figure 12–4 depicts a class diagram for the Single Sign-on Delegator. The client accesses the Single Sign-on Delegator component to invoke the remote security service components (SSOServiceProvider). The delegator (SSODelegator) retrieves security service configuration and service binding information from the system context (SSOContext) based on the client request. In other words, the client may be using a servlet, EJB, or Web services to access the identity management service. The delegator can also look up the service location via JNDI look-up or service registry look-up (if this is a Web service) according to the configuration details or service bindings. This can simplify the design construct for accommodating multiple security protocol bindings.
Figure 12–4 Single Sign-on Delegator class diagram
There are three important classes in Figure 12–4: SSOContext, SSODelegatorFactory and SSOServiceProvider. The SSOContext is a system context that encapsulates the service configuration and protocol binding for the remote service providers. It also stores the service status and the component reference (aka handler ID) for the remote service provider. The SSOContextImpl class is the implementation for the SSOContext class.
The SSODelegatorFactory defines the public interfaces to creating and closing a secure connection to the remote service provider. It takes a security token from the service requester so that it can validate the connection service request under a single sign-on environment. When a secure connection is established, the SSODelegatorFactory will also create a SSOToken used internally to reference it with the remote service provider. The SSODelegatorFactoryImpl class is the implementation for SSODelegatorFactory.
The SSOServiceProvider class defines the public interfaces for creating, closing, or reconnecting to a remote service. Figure 12–4 shows two examples of service providers (SSOServiceProviderImpl1 and SSOServiceProviderImpl2) that implement the public interfaces.
Participants and Responsibilities
Figure 12–5 shows a sequence diagram that depicts how to apply a delegator pattern to different identity management services via the Single Sign-on Delegator. In this scenario, the client wants to perform a single sign-on across different business services within the same domain (i.e., the same customer environment). The client (Client) refers to the service requester that initiates the service requests to multiple applications. The Single Sign-on Delegator (SSODelegator) connects to the remote business services. It retrieves the security service configuration information from the SSOContext service and looks up the service location via the naming service ServiceLocator. Finally, it keeps track of all connections using the service handlers and/or unique service IDs to perform single sign-on or global logout. The following shows the interaction between Client and SSODelegator:
- Client wants to invoke remote services via SSODelegator. SSODelegator verifies if Client is authorized to invoke remote security services.
- Upon successful verification, Client creates a delegator instance of _SSODelegator.
- SSODelegator retrieves service configuration (for example, EJB class name) and protocol bindings (for example, RMI method for EJB) from SSOContext.
- SSOContext sends service configurations and protocol bindings to SSODelegator.
- SSODelegator creates a single sign-on session using the method createSSODConnection and records the user ID and timestamp in the session information using the method setSessionInfo.
- Client initiates a request to invoke a remote service.
- SSODelegator creates a service connection to invoke the remote service provider using the method createService.
- SSODelegator retrieves the service configuration details (for example, EJB class) and protocol bindings for the remote service.
- SSODelegator looks up the service location of the remote security service using ServiceLocator (for example, via JNDI look-up for the remote EJB).
- SSODelegator invokes the remote security service by class name or URI.
- SSODelegator adds the component reference (also referred to as handler ID) to the SSOContext.
- Client requests to log out and close the connection of existing remote security services.
- SSODelegator begins to close the security service.
- SSODelegator initiates a closeSSOConnection to close the remote service.
- SSODelegator removes the component reference from SSOContext. It may also remove any existing session information by invoking the method removeSessionInfo.
- SSODelegator now completes closing the single sign-on session.
- SSODelegator notifies Client for successful global logout and closing security services.
Figure 12–5 Single Sign-on Delegator sequence diagram
Strategies
Using Single Sign-on Delegator and Assertion Builder Together
The Single Sign-on Delegator pattern provides a design framework for implementing single domain or cross-domain single sign-on using Liberty and SAML It also makes use of the Assertion Builder pattern to create SAML assertion statements for authentication, authorization, or attributes. Figure 12–6 depicts a use case scenario where a client (Client) needs to access multiple resources within the internal security domain. In order to access any resource, the client needs to authenticate with an identity service provider to establish the identity first. Once successful authentication is complete, it can initiate an authentication assertion request to access multiple resources within the single sign-on environment. The service provider (ServiceProvider) uses an identity server product to act as an identity service provider (IdentityProvider), which handles authentication for single sign-on purposes. The agent (WebAgent) is a Web server or application server plug-in
that intercepts the authentication requests using the Liberty protocol to provide single sign-on. In this scenario, the service provider runs an application server with a Liberty-compliant agent. Both SSODelegator and AssertionBuilder refer to the design patterns discussed earlier in this chapter. The following provides a step-by-step description of the interaction:
Figure 12–6 Single sign-on using Single Sign-on Delegator and Assertion Builder sequence diagram
- Client initiates a single sign-on request to access resources under the internal identity provider (or external identity provider).
- ServiceProvider creates an instance of single sign-on delegator.
- SSODelegator initiates an authentication assertion request with AssertionBuilder.
- Before AssertionBuilder creates an authentication assertion, Client needs to perform an authentication with the identity service provider first. Thus, ServiceProvider redirects the authentication request from Client.
- ServiceProvider initiates an HTTP authentication request with IdentityProvider.
- ServiceProvider obtains the relevant identity service provider identifier (there may be multiple identity service providers).
- ServiceProvider uses WebAgent (running on top of the application server) to respond to the authentication request.
- WebAgent redirects the authentication request to IdentityProvider.
- IdentityProvider processes the authentication request. It presents the authentication login form or HTML page to Client.
- Upon submission of the authentication login form by Client, IdentityProvider sends the authentication request response artifact to WebAgent.
- WebAgent sends the request with authentication response artifact to ServiceProvider.
- ServiceProvider processes the HTTP request with the authentication response artifact with IdentityProvider.
- IdentityProvider sends the HTTP response with the authentication assertion.
- ServiceProvider processes the authentication assertion.
- ServiceProvider sends the HTTP response with the authentication assertion.
- WebAgent returns the authentication assertion statement.
Global Logout Strategy
The Single Sign-on Delegator pattern can also act as a control mechanism for implementation of global logout, because it creates a connection to remote services and keeps track of each unique component reference to remote services
(handle ID). If a client is invalidated in the presentation tier, the Single Sign-on Delegator can issue a timely global logout to ensure session integrity. Once a client decides to sign out from all remote security services, the Single Sign-on Delegator can simply retrieve the service configuration (from SSOContext) or service location information (from ServiceLocator). Then they relay the logout request to each security service. Figure 12–7 depicts a use case scenario for global logout:
- Client initiates a request for global logout from all remote security services.
- SSODelegator verifies if Client is authorized to log out from all remote services.
- Upon successful verification, Client creates an instance of SSODelegator.
- SSODelegator retrieves the service configurations and protocol bindings from SSOContext.
- SSOContext sends the details of service configurations and protocol bindings to SSODelegator.
- SSODelegator fetches all service identifiers from all existing security service connections.
- SSODelegator looks up security service location from ServiceLocator.
- ServiceLocator returns the service location of remote services.
- SSODelegator initiates a global logout request to each remote service.
Figure 12–7 Global logout using Single Sign-on Delegator sequence diagram
Identity Termination / Revocation Strategy
If the user identity is terminated or revoked by the identity provider or the service provider, the identity management system should not allow the user to continue to create a single sign-on session. Liberty Phase 2 defines a federation termination notification protocol for handling identity termination or revocation (refer to Chapter 7). The Single Sign-on Delegator should be able to subscribe to the federation termination notification protocol.
If a user identity is terminated by the identity provider or service provider in the midst of a single sign-on session, the Single Sign-on Delegator should be able to terminate the single sign-on session using the global logout strategy.
Consequences
By employing the Single Sign-on Delegator pattern, developers will be able to reap the following benefits:
- Thwarting session theft. Session theft is a critical security flaw to identity management. The Single Sign-on Delegator creates a secure single sign-on session and delegates the service requests to relevant security services. Client requests must be authenticated with an identity provider before they can establish a secure single sign-on session. This can mitigate the risk of session theft.
- Addressing multiple sign-on issues. The Single Sign-on Delegator pattern supports a standards-based single sign-on framework that does not require users to sign on multiple times. There are security attacks that target application systems that are vulnerable due to multiple sign-on actions being required. Thus, the Single Sign-on Delegator can mitigate the multiple sign-on issues.
- More flexibility with a loosely coupled architecture. The Single Sign-on Delegator pattern provides a loosely coupled connection to remote security services. It minimizes the coupling between the clients and the remote identity management services. It hides the details of the handling of heterogeneous service invocation bindings and the service configuration of • multiple security service components from the clients. It also avoids specific-vendor product lock-in by disallowing clients to invoke the remote security services directly.
- Better availability of the remote security services. Architects and developers can implement or customize automatic recovery of the remote security services. They can also provide an alternate security services connection if the primary remote security service is not available.
- Improves scalability. Architects and developers can have multiple connections to the remote security services. Multiple instances of the remote security services will help improve scalability. In addition, architects and developers can cache some of the session variables or user identity information on behalf of the presentation tier components, which may help boost performance if there are a large number of simultaneous user connections.
Sample Code
Example 12–4 and Example 12–5 show a scenario where a service requester (for example, telecommunications subscriber) intends to access a variety of remote services via a primary service provider (for example, a telecommunication online portal). These sample code excerpts illustrate how to create a Single Sign-on Delegator pattern (using SSODelegatorFactoryImpl) to manage invoking remote security services using EJB. The Client creates an instance of the SSODelegatorFactoryImpl using the method getSSODelegator, and then invokes the method createSSOConnection to start a remote service. Upon completion, the Client invokes the method closeSSOConnection to close the remote service. The SSODelegatorFactoryImpl creates a single sign-on connection, invokes individual security service, and maintains session information. The code comment adds some annotation about how to add your own code to meet your local requirements or to extend the functionality.
In Example 12–4, the SSODelegatorFactoryImpl class initializes itself in the constructor by loading the list of “authorized” service providers (using the method initConfig). Then it creates a SSO token using the method createSSOToken to reference to all remote service connections. When the Client requests creating a single sign-on connection to a remote service, SSODelegatorFactoryImpl requires the Client to pass a security token for validation
Upon successful validation of the security token, the SSODelegatorFactoryImpl will look up the Java object class or URI of the remote service via the servicelocator method from the SSOContext. The SSOContext stores the service status and service configuration of the remote service. The service locator method is a service locator pattern that provides a few methods to look up the service location via EJB or Web services. The sample methods used in this code excerpt are examples only. The details can be found at [CJP2], pp. 315-340, or http://java.sun.com/blueprints/patterns/ServiceLocator.html.
The SSODelegatorFactoryImpl will then invoke the createService method of the remote service. It will update the service status “CREATED”). The component reference to the remote service will be added to SSOContext.
When the Client requests to close the remote service, the SSODelegatorFactoryImpl will invoke the closeService method of the remote service. It will update the service status to “CLOSED” and remove the component reference in the SSOContext.
Example 12–4 Sample SSODelegatorFactory implementation
package com.csp.identity; import java.util.HashMap; import com.csp.identity.*; public class SSODelegatorFactoryImpl implements com.csp.identity.SSODelegatorFactory { protected HashMap<String, com.csp.identity.SSOContextImpl> servicesMap = new HashMap(); // store serviceName, context protected HashMap<String, Object> SSOTokenMap = new HashMap(); // store serviceName, SSOToken protected static com.csp.identity.SSODelegatorFactoryImpl singletonInstance = null; protected String ssoToken; /** Constructor - Creates a new instance of SSODelegatorFactoryImpl */ private SSODelegatorFactoryImpl() { // load config file for all security authorized service // providers in Context initConfig(); createSSOToken(); } /** * Validate security token before creating, closing or * reconnecting to remote * service provider. * You can implement your security token validation process as * per local requirements. * You may want to reuse Credential Tokenizer to encapsulate * the security token. * * In this example, we'll always return true for demo purpose. */ private boolean validateSecurityToken(Object securityToken) { ノ return true; } /** * Create a SSO connection with the remote service provider * Need to pass a security token and the target service name. * The service locator will look up where the service name is. * And then invoke the remote object class/URI based on the * protocol binding. * * @param Object security token (for example, you can reuse * Credential Tokenizer) * @param String service name for the remote service provider */ public void createSSOConnection(Object securityToken, String serviceName) throws com.csp.identity.SSODelegatorException { if (validateSecurityToken(securityToken) == true) { System.out.println("Security token is valid"); try { // load Java object class (or URI) via // serviceLocator com.csp.identity.SSOContextImpl context = servicesMap.get(serviceName); String className = context.serviceLocator(serviceName); Class clazz = Class.forName(className); com.csp.identity.SSOServiceProvider serviceProvider = (com.csp.identity.SSOServiceProvider)clazz.newInstance(); // invoke remote security service provider serviceProvider.createService(context); // update status=CREATE context.setStatus(context.REMOTE_SERVICE_CREATED); // update servicesMap and context context.setCompRef(serviceProvider); servicesMap.remove(serviceName); servicesMap.put(serviceName, context); this.setSSOTokenMap(serviceName); } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); throw new com.csp.identity.SSODelegatorException("Class not found"); } catch (InstantiationException ie) { ie.printStackTrace(); throw new com.csp.identity.SSODelegatorException("Instantiation exception"); } catch (IllegalAccessException iae) { iae.printStackTrace(); throw new com.csp.identity.SSODelegatorException("Illegal access exception"); } } else { // update status=error System.out.println("Invalid security token presented!"); throw new com.csp.identity.SSODelegatorException("Invalid securitiy token"); } } /** * Close a SSO connection with the remote service provider * Need to pass a security token and the target * service name. * The service locator will look up where the * service name is. * And then invoke the remote object class/URI based on the * protocol binding. * * @param Object security token (for example, you can reuse * Credential Tokenizer) * @param String service name for the remote service provider */ public void closeSSOConnection(Object securityToken, String serviceName) throws com.csp.identity.SSODelegatorException { if (validateSecurityToken(securityToken) == true) { System.out.println("Security token is valid"); // load Java object class (or URI) // via serviceLocator com.csp.identity.SSOContextImpl context = servicesMap.get(serviceName); com.csp.identity.SSOServiceProvider serviceProvider = context.getCompRef(); if (serviceProvider == null) { throw new com.csp.identity.SSODelegatorException ("SSO connection not made."); } // invoke remote security service provider serviceProvider.closeService(); // update status=CLOSED context.setStatus(context.REMOTE_SERVICE_CLOSED); // update servicesMap and context context.removeCompRef(); servicesMap.remove(serviceName); servicesMap.put(serviceName, context); this.removeSSOTokenMap(serviceName); } else { // update status=error System.out.println("Invalid security token presented!"); throw new com.csp.identity.SSODelegatorException("Invalid securitiy token"); } } /** * Load the configuration into the SSODelegatorFactory * implementation so that * it will know which are the remote service providers * (including the * logical service name and the object class/URI for service * invocation. * * For demo purpose, we hard-coded a few examples here. * We can * also use * Apache Commons Configuration * to load a config.xml property * file. */ private void initConfig() { // load a list of "authorized" security // service providers // from the config file // and load into an array of SSOContext try { // create sample data com.csp.identity.SSOContextImpl context1 = new com.csp.identity.SSOContextImpl(); com.csp.identity.SSOContextImpl context2 = new com.csp.identity.SSOContextImpl(); context1.setServiceName("service1"); context1.setProtocolBinding("SOAP"); context2.setServiceName("service2"); context2.setProtocolBinding("RMI"); this.servicesMap.put("service1", context1); this.servicesMap.put("service2", context2); } catch (com.csp.identity.SSODelegatorException se) { se.printStackTrace(); } } /** * * You need to pass a security token before you can get the * SSODelegator instance. * Rationale: * 1. This ensures that only authenticated/authorized * subjects * can invoke the SSO Delegator. * (authentication and authorization requirements). * 2. No one can invoke the constructor directly (visibility * and segregation requirements). * 3. In addition, there is only a singleton copy (singleton * requirement). * * @param Object security token */ public static com.csp.identity.SSODelegatorFactoryImpl getSSODelegator(Object securityToken) { synchronized (com.csp.identity.SSODelegatorFactoryImpl.class) { if (singletonInstance==null) { singletonInstance = new com.csp.identity.SSODelegatorFactoryImpl(); } return singletonInstance; } } /** * This private method creates a SSO token to resemble a SSO * session has been * created to connect to remote security service providers. * In practice, this security token should be implemented in * any object type * based on local requirements. You can also reuse the * SecurityToken object * type from the Credential Tokenizer. * * For demo purpose, we'll use a string. * You can also use the * String format * to represent a base64 encoded format of a SSO token. */ private void createSSOToken() { // to be implemented this.ssoToken = "myPrivateSSOToken"; } /** * Register a SSOToken in the HashMap that a remote * service provider * connection has been made. * * @param String serviceName */ private void setSSOTokenMap(String serviceName) { this.SSOTokenMap.put(serviceName, this.ssoToken); } /** * Get a SSOToken in the HashMap that a remote service * provider * connection has been made. * * @param String serviceName * @return Object SSOToken (in this demo, we'll use a * String object) */ private Object getSSOTokenMap(String serviceName) { return (String)this.SSOTokenMap.get(serviceName); } /** * Remove a SSOToken from the HashMap that a remote * service provider * connection has been made. * * @param String serviceName */ private void removeSSOTokenMap(String serviceName) { this.SSOTokenMap.remove(serviceName); } /** * Get status from the remote service provider. * Need to pass a security token and the target service name. * The service locator will look up where * the service name is. * And then invoke the remote object class/URI based on the * protocol binding. * * @param Object security token * (for example, you can reuse Credential Tokenizer) * @param String service name for * the remote service provider */ public String getServiceStatus(Object securityToken, String serviceName) throws com.csp.identity.SSODelegatorException { if (validateSecurityToken(securityToken) == true) { System.out.println("Security token is valid"); // load Java object class (or URI) // via serviceLocator com.csp.identity.SSOContextImpl context = servicesMap.get(serviceName); return context.getStatus(); } else { // update status=error System.out.println("Invalid security token presented!"); throw new com.csp.identity.SSODelegatorException("Invalid securitiy token"); } } }
Example 12–5 shows sample code for implementing the SSOContext. The _SSOContextImpl class provides methods to add or get the service configuration and protocol binding for the remote service. When a new remote service is connected, the SSOContextImpl will add the component reference (aka handler ID) to the remote service using the method setCompRef and will update the status using the method setStatus.
Example 12–5 Sample SSOContext implementation
package com.csp.identity; import java.rmi.RemoteException; import java.util.HashMap; import java.util.Properties; import com.csp.identity.*; public class SSOContextImpl implements com.csp.identity.SSOContext { protected String serviceName; protected Properties configProps; protected String protocolBinding; protected HashMap sessionInfo = new HashMap(); protected com.csp.identity.SSOServiceProvider compRef; protected String status; protected final String REMOTE_SERVICE_CREATED = "CREATED"; protected final String REMOTE_SERVICE_CLOSED = "CLOSED"; protected final String REMOTE_SERVICE_ERROR = "ERROR"; protected enum ServiceStatus { CREATED, CLOSED, ERROR }; // Constructor - Creates a new instance // of SSOContextImpl public SSOContextImpl() throws com.csp.identity.SSODelegatorException { } /** * Set session information in a HashMap. * This stores specific * session variables * that are relevant to a particular remote secure service * provider * * @param String session variable name * @param String session variable value */ public synchronized void setSessionInfo(String sessionVariable, String sessionValue) { this.sessionInfo.put(sessionVariable, sessionValue); } /** * Get session information from a HashMap. This stores * specific session variables * that are relevant to a particular remote secure service * provider * Need to cast the object type upon return * * @return Object return in an Object (for example String). */ public synchronized Object getSessionInfo(String sessionVariable) { return this.sessionInfo.get(sessionVariable); } /** * Remove session information from a HashMap. The HashMap * stores specific session variables * that are relevant to a particular remote secure service * provider * * @param String session variable name */ public synchronized void removeSessionInfo(String sessionVariable) { this.sessionInfo.remove(sessionVariable); } /** * Get private configuration properties specific to a * particular * remote secure service provider. This object needs to be * loaded during * initConfig(), by the constructor or manually * * @return Properties a Properties object */ public java.util.Properties getConfigProperties() { return configProps; } /** * Set private configuration properties specific to a * particular * remote secure service provider. This object needs to be * loaded during * initConfig(), by the constructor or manually * * @param Properties a Properties object */ public void setConfigProperties(java.util.Properties configProps) { this.configProps = configProps; } /** * Get protocol binding for the remote security service * provider * * @return String protocol binding, for example SOAP, RMI * (arbitrary name) */ public String getProtocolBinding() { return this.protocolBinding; } /** * Set protocol binding for the remote security service * provider * * @param String protocol binding, for example SOAP, RMI (arbitrary * name) */ public void setProtocolBinding(String protocolBinding) { this.protocolBinding = protocolBinding; } /** * Get service name of the remote security service provider. * This name needs to match the field 'serviceName' in the * SSOServiceProvider implementation classes * * @return String service name, for example service1 */ public String getServiceName() { return this.serviceName; } /** * set service name * * @param String logical remote service name, for example service1 * **/ public void setServiceName(String serviceName) { this.serviceName = serviceName; } /** * Get component reference * * @return SSOServiceProvider component * reference to be stored * in the HashMap * once a connection is created **/ public com.csp.identity.SSOServiceProvider getCompRef() { return this.compRef; } /** * Set component reference * * @param SSOServiceProvider component * reference to be stored * in the HashMap * once a connection is created **/ public void setCompRef(com.csp.identity.SSOServiceProvider compRef) { this.compRef = compRef; } /** * Remove component reference * **/ public void removeCompRef(){ this.compRef = null; } /** * Look up the class name or URI by the service name * * This example hardcodes one class name for demo. * You may want to replace it by a Service Locator pattern * * @param String service name to look up * @return String class name (or URI) corresponding service **/ public String serviceLocator(String serviceName) { // This example shows 2 remote // security service providers // hard-coded for demo purpose. Refer to the book’s // website for sample code download. // You may want to use a Service Locator pattern here if (serviceName.equals("service1")) { return "com.csp.identity.SSOServiceProviderImpl1"; } if (serviceName.equals("service2")) { return "com.csp.identity.SSOServiceProviderImpl2"; } return "com.csp.identity.SSOServiceProviderImpl2"; } /** * set status of the remote service * * @param String status */ public void setStatus(String status) { this.status = status; } /** * get status of the remote service * * @return String status */ public String getStatus() { return this.status; } }
Security Factors and Risks
- Caching user identity information. Caching user identity information in shared memory (for example, implemented in a hash table) is a mechanism used by the Single Sign-on Delegator to improve performance. However, there is also a security risk if any other application client can access the shared memory. Thus, architects and developers need to ensure that cached information is protected and is accessible to the Single Sign-on Delegator only.
- Logging and audit risks. Security compliance and local regulations often require all user login and security activities to be logged and audited through out the user sign-on session. The logging and audit requirements can help to track down any unusual password changes or suspicious transaction changes under the single sign-on session. The security risk is extremely high if the single sign-on session control does not log all user authentication and access control changes throughout the session for audit control.
Reality Check
- Too many abstraction layers. Single Sign-on Delegator brings the benefit of loosely coupled architecture by creating an abstraction layer for remote security services. However, if the remote security services have more than one abstraction layer, multiple abstraction layers of remote service invocations will create substantial performance overhead. From experience, one to two abstraction layers would be reasonable.
- Supporting multiple circles of trust. Currently, Liberty specification 2.0 does not support integrating multiple circles of trust or interoperating with multiple identity service providers simultaneously (for example, when a client wants to perform single sign-on in two different circles of trust or in two different types of single sign-on environments). Single Sign-on Delegator is not designed to support multiple circles of trust, because it is a delegate design approach that simplifies the connection of remote security services. The support of interoperating with multiple identity service providers is dependent on the Liberty implementation or the remote security services.
Related Patterns
- Assertion Builder. A Single Sign-on Delegator can delegate the creation of SAML assertion statements via the Assertion Builder to a remote security service provider that assembles and generates a SAML authentication or authorization decision statement. This does not require adding the business logic of managing SAML assertions in the Single Sign-on Delegator.
- Credential Tokenizer. A Single Sign-on Delegator can delegate the encapsulation of user credentials to the Credential Tokenizer. This does not require building additional business logic to handle user credentials in the Single Sign-on Delegator. Architects and developers can also reuse the credential tokenizer functions for other applications (for example, EDI messaging applications) without using Single Sign-on Delegator.
- Service Locator. The Single Sign-on Delegator pattern uses a Service Locator pattern to look up the service location of the remote security services. In other words, it delegates the service look-up function to a Service Locator, which can be implemented as a JNDI look-up or a UDDI service discovery. Refer to http://java.sun.com/blueprints/patterns/ServiceLocator.html and [CJP2] for details.
Credential Tokenizer Pattern
Problem
You need a flexible mechanism to encapsulate a security token that can be used by different security infrastructure providers.
There are different forms of user credentials (also referred to as security tokens), such as username/passwords, binary security tokens (for example, X.509v3 certificates), Kerberos tickets, SAML tokens, smart card tokens and biometric samples. Most security tokens are domain-specific. To encapsulate these user credentials for use with different security product architectures, developers have to modify the security token processing routine to accommodate individual security product architectures, which depends on the specific security specification the security product uses. A user credential based on a digital certificate will be processed differently than that of a Kerberos ticket. There is no consistent and flexible mechanism for using a common user credential tokenizer that supports different types of security product architectures supporting different security specifications.
Forces
- You need a reusable component that helps to extract processing logic to handle creation and management of security tokens instead of embedding them in the business logic or the authentication process.
- You want to shield off the design and implementation complexity using a common mechanism that can accommodate a security credential and interface with a supporting security provider that makes use of them.
Solution
Use a Credential Tokenizer to encapsulate different types of user credentials as a security token that can be reusable across different security providers.
A Credential Tokenizer is a security API abstraction that creates and retrieves the user identity information (for example, public key/X.509v3 certificate) from a given user credential (for example, a digital certificate issued by a Certificate Authority). Each security specification has slightly different semantics or mechanisms to handle user identity and credential information. These include the following characteristics:
- Java applications that need to access user credentials or security tokens from different application security infrastructures.
- Web Services security applications that need to encapsulate a security token, such as username token or binary token, in the SOAP message.
- Java applications that support SAML or Liberty that need to include an authentication credential in the SAML assertion request or response.
- Java applications that need to retrieve user credentials for performing SSO with legacy applications.
To build a Credential Tokenizer, developers need to identify the service, authentication scheme, application provider, and underlying protocol bindings. For example, in a SOAP communication model, the service requestor is required to use a digital certificate as a binary security token for accessing a service end-point. In this case, the service configuration specifies the X.509v3 digital certificate as the security token and SOAP messages and SOAP over HTTPS as the protocol binding. Similarly, in a J2EE application, the client is required to use a Client-certificate for enabling mutual authentication. In this case, the authentication requirements specify an X.509v3 digital certificate as the security token and SOAP over HTTPS as the protocol binding, but the request is represented as HTML generated by a J2EE application using a JSP or a servlet.
Credential Tokenizer provides an API abstraction mechanism for constructing security tokens based on a defined authentication requirement, protocol binding, and application provider. It also provides API mechanisms for retrieving security tokens issued by a security infrastructure provider.
Structure
Figure 12–8 depicts a class diagram of the Credential Tokenizer. The Credential Tokenizer can be used to create different security tokens (SecurityToken), including username token and binary tokens (X.509v3 certificate. When creating a security token, the Credential Tokenizer creates a system context (TokenContext) that encapsulates the token type, the name of the principal, the service configuration, and the protocol binding that the security token supports.
Figure 12–8 Credential Tokenizer class diagram
There are two major objects in the Credential Tokenizer: SecurityToken and TokenContext. The SecurityToken is a base class that encapsulates any security token. It can be extended to implement username token (UsernameToken), binary token (_BinaryToken), and certificate token (X509v3CertToken). In this pattern, Username token is used to represent a user identity using Username Password. Binary tokens are used to represent a variety of security tokens that resemble a user identity using binary text form (such as Kerberos Tickets). Certificate tokens denote digital certificates issued to represent a user identity. An X.509v3 certificate is a common form of certificate token.
The TokenContext class refers to the system context used to create security tokens. It includes information such as the security token type, service configuration, and protocol binding for the security token. This class defines public interfaces only to set or get the security token information. TokenContextImpl is the implementation for TokenContext.
Participants and Responsibilities
Figure 12–9 depicts the Credential Tokenizer sequence diagram—how a client makes use of the Credential Tokenizer to create a security token. For example, the Client may be a service requester that is required to create the Username Password-token to represent in the WS-Security headers of a SOAP message. The CredentialTokenizer denotes the credential tokenizer that creates and manages user credentials. The UserCredential denotes the actual Credential Token, such as username/password or a X.509v3 digital certificate. The following sequences describe the interaction between the Client, CredentialTokenizer, and UserCredential:
- Client creates an instance of CredentialTokenizer.
- CredentialTokenizer retrieves the service configuration and the protocol bindings for the target service request.
- CredentialTokenizer retrieves the user credentials from SecurityProvider according to the service configuration. For example, it extracts the key information from an X.509v3 certificate.
- CredentialTokenizer creates a security token from the user credentials just retrieved.
- Upon successful completion of creating the security token, CredentialTokenizer returns the security token to Client.
Figure 12–9 Credential Tokenizer sequence diagram
Strategies
Service Provider Interface Approach
Using a service provider interface approach to define the public interfaces for different security tokens will be more flexible and adaptive for different security tokens and devices. For example, certificate tokens may differ in vendor implementation. Developers can use the same public interfaces to support different credential token implementations and meet the requirements of different platforms and service providers without customizing the APIs for specific devices.
Protocol Binding Strategy
As with the Assertion Builder pattern, it is possible that the same client may be using the Credential Tokenizer to encapsulate user credentials as a security token in a SOAP message. To accommodate such use, developers can employ a custom service configuration look-up function (for example, refer to getProtocolBinding method in the SSOContext discussed in SSO Delegator pattern) to determine the data transport and application environment requirements. In this way, the common processing logic of the user credential processing and security token encapsulation can be reused.
Consequences
- Supports SSO. The Credential Tokenizer pattern helps in capturing authentication credentials for multifactor authentication. It also helps in using “shared state” (the “shared state” mechanism allows a login module to put the authentication credentials into a shared map and then passes it to other login modules) among authentication providers in order to establish single sign-on, where the Credential Tokenizer can be used for retrieving the SSO token and providing SSOToken on demand for requesting applications.
- Provides a vendor-neutral credential handler. The Credential Tokenizer pattern wraps vendor-specific APIs using a generic mechanism in order to create or retrieve security tokens from security providers.
- Enables transparency by encapsulating multiple identity management infrastructures. The Credential Tokenizer pattern encapsulates any form of security token as a credential token and thus eases integration and enables interoperability with different identity management infrastructures.
Sample Code
Example 12–6 shows a sample code excerpt for creating a Credential Tokenizer. The CredentialTokenizer creates an instance of TokenContextImpl, which provides a system context for encapsulation of the security token created. To create a security token, you need to define the security token type using the method setTokenType. Then you need to create the security token using the method createToken, which invokes the constructor of the target security token class (for example, UsernameToken).
Example 12–6 Sample Credential Tokenizer implementation
package com.csp.identity; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; public class CredentialTokenizer { protected com.csp.identity.TokenContextImpl context; protected com.csp.identity.UsernameToken usernameToken; protected java.security.cert.X509Certificate cert; // in dev/production, you won't put the subject, principal // or password here protected final String testPrincipal = "username"; protected final String testPassword = "password"; /** Constructor - Creates a new instance of CredentialTokenizer */ public CredentialTokenizer() { context = new com.csp.identity.TokenContextImpl(); //-------------For UsernameToken------------------------- context.setTokenType (com.csp.identity.UsernameToken.TOKEN_TYPE); context.createToken(testPrincipal, testPassword); //--------------------------------------------------**/ } public static void main(String[] args) { new CredentialTokenizer(); } }
Example 12–7 shows a sample implementation for the TokenContext. The TokenContextImpl is an implementation of the public interfaces defined in the TokenContext class. The former can provide methods to fetch the name of the principal (getPrincipal method) and the protocol binding for the security token (getProtocolBinding method).
Example 12–7 Sample TokenContext implementation
package com.csp.identity; public class TokenContextImpl implements com.csp.identity.TokenContext { protected com.csp.identity.UsernameToken usernameToken; protected com.csp.identity.BinaryToken binaryToken; protected com.csp.identity.X509CertToken x509CertToken; protected String tokenType; /** Constructor - Creates a new instance of CredentialTokenizer */ public TokenContextImpl() { usernameToken = null; binaryToken = null; x509CertToken = null; } /** * Define token type * - use the constant in each security token subclass to * define * * @param tokenType security token type, for example USERNAME_TOKEN, * X509CERT_TOKEN, BINARY_TOKEN, KERBEROS_TICKET **/ public void setTokenType(String tokenType) { this.tokenType = tokenType; } /** * create security token based on the subject, principal * and security token * * @param principal principal * @param securityToken security token can be * binary,username, X.509v3 certificate, * Kerberos ticket, etc * **/ public void createToken(String principal, Object securityToken) { if (this.tokenType.equals (com.csp.identity.UsernameToken.TOKEN_TYPE)) { //System.out.println("create a usernametoken..."); usernameToken = new com.csp.identity.UsernameToken(principal, (String)securityToken); } else if (this.tokenType.equals(com.csp.identity.BinaryToken.TOKEN_TYPE)) { System.out.println("create a binary token..."); binaryToken = new com.csp.identity.BinaryToken(principal, (String)securityToken); } } /** * get security token * * @return Object any security token type, * for example String, X.509v3 certificate **/ public Object getToken() { if (this.tokenType.equals(com.csp.identity.UsernameToken.TOKEN_TYPE)) { //System.out.println("get a usernametoken..."); return (Object)usernameToken.getToken(); } else if (this.tokenType.equals(com.csp.identity.BinaryToken.TOKEN_TYPE)) { //System.out.println("get a binary token..."); return (Object)binaryToken.getToken(); } else return null; } /** * get principal * * @return principal return principal in String **/ public String getPrincipal() { if (this.tokenType.equals(com.csp.identity.UsernameToken.TOKEN_TYPE)) { //System.out.println("get principal..."); return usernameToken.getPrincipal(); } else if (this.tokenType.equals(com.csp.identity.BinaryToken.TOKEN_TYPE)) { //System.out.println("get principal..."); return binaryToken.getPrincipal(); } else return null; } /** * get protocol binding for the security token * * @return protocolBinding protocol binding in String **/ public String getProtocolBinding() { return null; } }
Example 12–8 shows a sample implementation of the username token used in previous code examples (refer to Figure 12–15 and Figure 12–16). The UsernameToken class is an extension of the base class SecurityToken. It provides methods to define and retrieve information regarding the principal name, subject’s IP address, subject’s DNS address and the password.
Example 12–8 Sample UsernameToken implementation
package com.csp.identity; public class UsernameToken extends com.csp.identity.SecurityToken { protected static String password; static final String TOKEN_TYPE = "USERNAME_TOKEN"; /** Constructor - create usernameToken * * In future implementation, the constructor should be * private, and this class * should provide a getInstance() to fetch the instance. */ public UsernameToken(String principal, String password) { this.principal = principal; this.password = password; } /** * Get token ID from the binary token * * @return binaryToken security token in binary form */ public String getToken() { return this.password; } }
Security Factors and Risks
The Credential Tokenizer pattern is essential to encapsulating user credentials and user information to meet authentication and non-repudiation security requirements. One important security factor for building reliable credential tokenizers is the identity management infrastructure and whether the keys are securely managed prior to the credential processing. The following are security factors and risks associated with the Credential Tokenizer pattern.
- Username password token. Username password tokens are highly vulnerable to attacks by using a password dictionary.
- X.509v3 certificate token. Certificate token is a reliable security token and is stronger than Username Password token. However, it may be susceptible to human error during the management of the distribution of digital certificates and the timely revocation of certificates.
- Key management strategy. The security factor of key management strategy defines the process of generating key pairs, storing them in safe locations, and retrieving them. The generation of SAML assertion statements and signed SOAP messages using WS-Security is key management strategy. If the key management strategy and the infrastructures are not in place, the user credential token processing will be at risk.
Reality Check
- Should we use username/password as a security token? Some security architects insist that the username/password pair is not secure enough and should not be used as a security token. To mitigate the potential risk of a weak password, security architects should reinforce strong password policies and adopt a flexible security token mechanism such as Credential Tokenizer to accommodate different types of security tokens for future extension and interoperability.
- What other objects can be encapsulated as security token? You can embed different types of security tokens in the Credential Tokenizer, not just username/password or digital certificate. For example, you can embed binary security tokens, because they can be encapsulated as a SAML token for an authentication assertion statement. In addition, you can also add the REL token (which denotes the rights, usage permissions, constraints, legal obligations, and license terms pertaining to an electronic document) based on the eXtensible Rights Markup Language (XrML).
Related Patterns
- Secure Pipe. The Secure Pipe pattern shows how to secure the connection between the client and the server, or between servers when connecting between trading partners. In a complex distributed application environment, there will be a mixture of security requirements and constraints between clients, servers, and any intermediaries. Standardizing the connection between external parties using the same platform and security protection mechanism may not be viable.