Fork me on GitHub

Pre-Authenticated Login

Oak provides two different mechanisms to create pre-authentication that doesn't involve the repositories internal authentication mechanism for credentials validation.

Pre-Authentication combined with Login Module Chain

This first variant allows to support 3rd party login modules that wish to provide the login context with pre authenticated login names, but still want to rely on the rest of the Oak's login module chain. For example an external SSO login module can extract the userid from a servlet request and use it to authenticate against the repository. But instead of re-implementing the user lookup and subject population (and possible external user synchronization) it just informs any subsequent login modules that the credential validation was already successful.

The key to understand this mechanism is the PreAuthenticatedLogin marker class, which is pushed to the shared state of the login context and which indicates to any subsequent LoginModule that the credentials present in the state already have been verified and thus can be trusted.

This setup is particularly recommended in a OSGi setup that includes Apache Sling on top of the Oak repository but still requires user information to be synchronized into the repository.

How it works

The basic steps of the pre-authentication in combination with regular JAAS login module chain are outlined as follows:

  1. verify the identity in the layer on top of the JCR repository (e.g. in a custom Sling Authentication Handler)
  2. pass a custom, non-public Credentials implementation to the repository login
  3. create a custom login module that only supports these dedicated credentials and pushes both a new instance of PreAuthenticatedLogin and other information required and processed by subsequent login modules (e.g. credentials and user name).
  4. make sure the subsequent login modules in the JAAS configuration are capable to deal with the PreAuthenticatedLogin and the additional information and will properly populate the subject and optionally synchronize user information or create login tokens.
Example

Example implementation of LoginModule#login that pushes the PreAuthenticatedLogin marker to the shared state:

public class PreAuthLoginModule extends AbstractLoginModule {

[...]

    @Overwrite
    public boolean login() throws LoginException {
        Credentials credentials = getCredentials();
        if (credentials instanceof MyPreAuthCredentials) {
            userId = ((MyPreAuthCredentials) credentials).getUserId();
            if (userId == null) {
                log.debug("Could not extract userId/credentials");
            } else {
                sharedState.put(SHARED_KEY_PRE_AUTH_LOGIN, new PreAuthenticatedLogin(userId));
                sharedState.put(SHARED_KEY_CREDENTIALS, new SimpleCredentials(userId, new char[0]));
                sharedState.put(SHARED_KEY_LOGIN_NAME, userId);
                log.debug("login succeeded with trusted user: {}", userId);
            }
        }

        [...]
        
        // subsequent login modules need to succeed and process the 'PreAuthenticatedLogin'
        return false;
    }
    
    @Overwrite
    public boolean commit() {
        // this module leaves subject population to the subsequent modules 
        // that already handled the login with 'PreAuthenticatedLogin' marker.
        return false;
    }
}

Pre-Authentication without Repository Involvement

Like in Jackrabbit-core the repository internal authentication verification can be skipped by calling Repository#login() or Repository#login(null, wspName). In this case the repository implementation expects the verification to be performed prior to the login call.

This behavior is provided by the default implementation of the LoginContextProvider [1] which expects a Subject to be available with the current java.security.AccessControlContext. However, in contrast to Jackrabbit-core the current implementation does not try to extend the pre-authenticated subject but skips the internal verification step altogether.

Options to modify the default behavior

Since the LoginContextProvider is a configurable with the authentication setup OAK users also have the following options by providing a custom LoginContextProvider:

  • Disable pre-authentication by not trying to retrieve a pre-authenticated Subject.
  • Add support for extending the pre-authenticated subject by always passing writable subjects to the JaasLoginContext
  • Dropping JAAS altogether by providing a custom implementation of the org.apache.jackrabbit.oak.spi.security.authentication.LoginContext [2] interface.
Example

Example how to use this type of pre-authentication:

String userId = "test";
/**
 * Retrive valid principals e.g. by using Jackrabbit or Oak API:
 * - PrincipalManager#getPrincipal and/or #getGroupMembership
 * - PrincipalProvider#getPrincipals(String userId)
 */
Set<? extends Principal> principals = getPrincipals(userId);
AuthInfo authInfo = new AuthInfoImpl(userId, Collections.<String, Object>emptyMap(), principals);
Subject subject = new Subject(true, principals, Collections.singleton(authInfo), Collections.<Object>emptySet());
Session session;
try {
    session = Subject.doAsPrivileged(subject, new PrivilegedExceptionAction<Session>() {
        @Override
        public Session run() throws Exception {
            return login(null, null);
        }
    }, null);
} catch (PrivilegedActionException e) {
    throw new RepositoryException("failed to retrieve session.", e);
}