Assertion Builder PatternProblemYou 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
SolutionUse 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. Figure 12-1. Assertion Builder logical architectureStructureFigure 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-2. Assertion Builder class diagramThe 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 ResponsibilitiesFigure 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 diagramThis 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.
StrategiesProtocol Binding StrategyIt 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 StrategyDevelopers 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 StrategyAlthough 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-onThe 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. ConsequencesBy employing the Assertion Builder pattern, developers will be able to benefit in the following ways:
Sample CodeExample 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 implementationpackage 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 = "www.coresecuritypattern.com"; protected static final String destinationSite = "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 codepackage 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 implementationpackage 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 RisksThe 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.
Reality Check
Related Patterns
Single Sign-on (SSO) Delegator PatternProblemYou 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
SolutionUse 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. StructureFigure 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 diagramThere 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 ResponsibilitiesFigure 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:
Figure 12-5. Single Sign-on Delegator sequence diagramStrategiesUsing Single Sign-on Delegator and Assertion Builder TogetherThe 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 diagramGlobal Logout StrategyThe 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:
Figure 12-7. Global logout using Single Sign-on Delegator sequence diagramIdentity Termination / Revocation StrategyIf 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. ConsequencesBy employing the Single Sign-on Delegator pattern, developers will be able to reap the following benefits:
Sample CodeExample 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 implementationpackage 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 implementationpackage 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
Reality Check
Related Patterns
Credential Tokenizer PatternProblemYou 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
SolutionUse 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:
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. StructureFigure 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 diagramThere 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 ResponsibilitiesFigure 12-9 depicts the Credential Tokenizer sequence diagramhow 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:
Figure 12-9. Credential Tokenizer sequence diagram
StrategiesService Provider Interface ApproachUsing 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 StrategyAs 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
Sample CodeExample 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 implementationpackage 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 implementationpackage 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 1215 and Figure 1216). 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 implementationpackage 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 RisksThe 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.
Reality Check
Related Patterns
|