Elytron Identity Cache and Single Sign-On

Version 7

    Overview

     

    The intent of this document is to have an identity caching mechanism in order to allow authentication mechanisms to cache their identities as part of the authentication and authorization process.

     

    Identity Cache Design Overview

     

    An IdentityCache represents a reliable storage where previously authenticated and authorized identities are stored. Authentication mechanisms that support caching of identities are responsible to create IdentityCache instances and provide how these identities are actually cached.

     

    private IdentityCache createIdentityCache(HttpServerRequest request, boolean createSession) {
            return new IdentityCache() {
                @Override
                public void put(SecurityIdentity  identity) {
                    HttpScope session = getSessionScope(request, createSession);
    
    
                    if (!session.exists()) {
                        return;
                    }
    
    
                    session.setAttachment(CACHED_IDENTITY_KEY, new CachedIdentity(getMechanismName(), identity));
                }
    
    
                @Override
                public CachedIdentity get() {
                    HttpScope session = getSessionScope(request, createSession);
    
    
                    if (!session.exists()) {
                        return null;
                    }
    
    
                    return (CachedIdentity) session.getAttachment(CACHED_IDENTITY_KEY);
                }
    
    
                @Override
                public CachedIdentity remove() {
                    HttpScope session = getSessionScope(request, createSession);
    
    
                    if (!session.exists()) {
                        return null;
                    }
    
    
                    CachedIdentity cachedIdentity = get();
    
    
                    session.setAttachment(CACHED_IDENTITY_KEY, null);
    
    
                    return cachedIdentity;
                }
            };
    }
    
    

     

    The example above shows an IdentityCache implementation that is capable of managing cached identities using a session scope. The IdentityCache interface provides only a few callback methods that are called during the authorization process in order to allow a mechanism to update its cache depending on the result of the authorization request.

     

    Mechanisms using an IdentityCache must use a specific authorization callback when authorizing their identities. The CachedIdentityAuthorizeCallback callback allows mechanisms to provide their own IdentityCache implementation and can be used to:

     

    • Authorize an identity and, if successful, cache it.

     

    IdentityCache identityCache = createIdentityCache(request, true);
    
    
    if (identityCache != null) {
        CachedIdentityAuthorizeCallback authorizeCallback = new CachedIdentityAuthorizeCallback(username, identityCache);
    
        try {
            callbackHandler.handle(new Callback[]{authorizeCallback});
        } catch (IOException | UnsupportedCallbackException e) {
            throw new HttpAuthenticationException(e);
        }
    
        return authorizeCallback.isAuthorized();
    }
    
    return false;
    

     

    • Re-authorize a previously cached identity

     

    IdentityCache identityCache = createIdentityCache(request, false);
    
    
    if (identityCache != null) {
        CachedIdentityAuthorizeCallback authorizeCallback = new CachedIdentityAuthorizeCallback(identityCache);
    
    
        try {
            callbackHandler.handle(new Callback[]{authorizeCallback});
        } catch (IOException | UnsupportedCallbackException e) {
            throw new HttpAuthenticationException(e);
        }
    
    
        return authorizeCallback.isAuthorized();
    }
    
    
    return false;
    

    Single Sign-On Design Overview

     

    Single Sign-On leverages the Identity Cache API in order to support Cookie-Based Single Sign-On, where the SSO session is referenced by a cookie and managed by a built-in HTTP Authentication Mechanism.

     

    The SingleSignOnServerMechanismFactory is a special implementation of HttpServerAuthenticationMechanismFactory that can be used to enable SSO to any existing HTTP Authentication Mechanism.

     

    HttpServerAuthenticationMechanismFactory delegate = // the factory that actually provides the authentication mechanisms supported by an application
    SingleSignOnSessionFactory ssoSessionFactory      = // the factory that creates SSO sessions
    SingleSignOnConfiguration ssoConfiguration        = // the sso configuration such as those related with the SSO cookie (name, domain, path, secure, etc)
    SingleSignOnServerMechanismFactory ssoMechFactory = new SingleSignOnServerMechanismFactory(delegate, ssoSessionFactory, ssoConfiguration);
    

     

    The SingleSignOnSessionFactory allows implementors to provide different caching strategies for SSO sessions. While the SingleSignOnConfiguration provides all the configuration options supported by the mechanism itself.

     

    The SingleSignOnSessionFactory is basically an IdentityCache. At this regard, implementations should follow the same considerations as discussed earlier in the section on Identity Cache Design Overview. In addition to methods related with the caching of identities, this interface provides more methods in order to actually manage SingleSignOnSession instances.

     

    By default, Elytron provides a DefaultSingleSignOnSessionFactory that manages SSO sessions based on a single cache for both identities and local sessions. This implementation also supports Back-Channel Single Logout, which invalidates active local sessions on each participant of a specific SSO session.

     

    Enabling Single Sign-On in Undertow Subsystem

     

    The SSO capabilities provided by Elytron allows users to enable SSO on a per-domain basis. Differently than what we have today where SSO is configured for a specific server/host as follows:

     

    <server name="default-server">
        <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>
        <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
        <host name="default-host" alias="localhost">
            <location name="/" handler="welcome-content"/>
            <filter-ref name="server-header"/>
            <filter-ref name="x-powered-by-header"/>
            <single-sign-on domain="localhost"/>
        </host>
    </server>
    

     

    With the new configuration, we should be able to enable SSO just like that:

     

    <application-security-domains>
        <application-security-domain security-domain="other" http-authentication-factory="application-http-authentication"/>
        <application-security-domain security-domain="example-single-sign-on-domain" http-authentication-factory="example-http-authentication">
            <single-sign-on domain="localhost"/>
        </application-security-domain>
    </application-security-domains>