In order to understand how login modules work and how Oak can help providing extension points we need to look at how JAAS authentication works in general and discuss where the actual credential-verification is performed.
The following section is copied and adapted from the javadoc of javax.security.auth.spi.LoginModule. The authentication process within the LoginModule
proceeds in two distinct phases, login and commit phase:
Phase 1: Login
LoginModule
's login
method gets invoked by the LoginContext
's login
method.login
method for the LoginModule
then performs the actual authentication (prompt for and verify a password for example) and saves its authentication status as private state information.LoginModule
's login method either returns true
(if it succeeded) or false
(if it should be ignored), or throws a LoginException
to specify a failure. In the failure case, the LoginModule
must not retry the authentication or introduce delays. The responsibility of such tasks belongs to the application. If the application attempts to retry the authentication, the LoginModule
's login
method will be called again.Phase 2: Commit
LoginContext
's overall authentication succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules succeeded), then the commit
method for the LoginModule
gets invoked.commit
method for a LoginModule
checks its privately saved state to see if its own authentication succeeded.LoginContext
authentication succeeded and the LoginModule
's own authentication succeeded, then the commit
method associates the relevant Principals (authenticated identities) and Credentials (authentication data such as cryptographic keys) with the Subject located within the LoginModule
.LoginContext
's overall authentication failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules did not succeed), then the abort
method for each LoginModule
gets invoked. In this case, the LoginModule
removes/destroys any authentication state originally saved.Very simply put, all the login modules that participate in JAAS authentication are configured in a list and can have flags indicating how to treat their behaviors on the login()
calls.
JAAS defines the following module flags:
(The following section is copied and adapted from the javadoc of javax.security.auth.login.Configuration)
The overall authentication succeeds only if all Required and Requisite LoginModules succeed. If a Sufficient LoginModule is configured and succeeds, then only the Required and Requisite LoginModules prior to that Sufficient LoginModule need to have succeeded for the overall authentication to succeed. If no Required or Requisite LoginModules are configured for an application, then at least one Sufficient or Optional LoginModule must succeed.
Within the scope of JCR Repository.login
is used to authenticate a given user. This method either takes a Credentials
argument if the validation is performed by the repository itself or null
in case the user has be pre-authenticated by an external system.
Furthermore JCR defines two types of Credentials
implementations:
The following variants exist for the repository login itself:
Repository.login()
: equivalent to passing null
credentials and the default workspace name.Repository.login(Credentials credentials)
: login with credentials to the default workspace.Repository.login(String workspace)
: login with null
credentials to the workspace with the specified name.Repository.login(Credentials credentials, String workspaceName)
JackrabbitRepository.login(Credentials credentials, String workspaceName, Map<String, Object> attributes)
: in addition allows to pass implementation specific session attributes.See javax.jcr.Repository and org.apache.jackrabbit.api.JackrabbitRepository for further details.
In addition JCR defines Session.impersonate(Credentials)
to impersonate another user or - as of JSR 333 - clone an existing session.
The Oak API contains the following authentication related methods and interfaces
Subject.
ContentRepository.login(Credentials, String)
: The Oak counterpart of the JCR login.ContentSession.getAuthInfo()
: exposes the AuthInfo
associated with the ContentSession
.In the the package org.apache.jackrabbit.oak.spi.security.authentication
Oak 1.0 defines some extensions points that allow for further customization of the authentication.
LoginContextProvider
: Configurable provider of the LoginContext
(see below)LoginContext
: Interface version of the JAAS LoginContext aimed to ease integration with non-JAAS componentsAuthentication
: Aimed to validate credentials during the first phase of the (JAAS) login process.In addition this package contains various utilities and base implementations. Most notably an abstract login module implementation (AbstractLoginModule) as described below and a default implementation of the AuthInfo interface (AuthInfoImpl).
This package also contains a abstract LoginModule
implementation (AbstractLoginModule) providing common functionality. In particular it contains Oak specific methods that allow subclasses to retrieve the SecurityProvider
, a Root
and accesss to various security related interfaces (e.g. PrincipalManager
).
Subclasses are required to implement the following methods:
getSupportedCredentials()
: return a set of supported credential classes. See also section Supported Credentialslogin()
: The login method defined by LoginModule
commit()
: The commit method defined by LoginModule
public class TestLoginModule extends AbstractLoginModule { private Credentials credentials; private String userId; private Set<? extends Principal> principals; @Nonnull @Override protected Set<Class> getSupportedCredentials() { return ImmutableSet.of(TestCredentials.class); } @Override public boolean login() throws LoginException { credentials = getCredentials(); if (validCredentials(credentials)) { this.credentials = credentials; this.userId = getUserId(credentials); this.principals = getPrincipals(userId); return true; } return false; } @Override public boolean commit() throws LoginException { if (credentials != null) { if (!subject.isReadOnly()) { subject.getPublicCredentials().add(credentials); if (principals != null) { subject.getPrincipals().addAll(principals); } AuthInfo authInfo = new AuthInfoImpl(userId, Collections.EMPTY_MAP, principals); setAuthInfo(authInfo, subject); } return true; } return false; } }
Since Oak 1.5.1 the extensions additionally contain a dedicated interface that eases the support for different Credentials
in the package space org.apache.jackrabbit.oak.spi.security.authentication.credentials
:
Credentials
classes and some common utility methods.SimpleCredentials
A description of the various requirements covered by Oak by default as well as the characteristics of the corresponding implementations can be found in section Authentication: Implementation Details.
See section differences for comprehensive list of differences wrt authentication between Jackrabbit 2.x and Oak.
The configuration of the authentication setup is defined by the AuthenticationConfiguration. This interface provides the following method:
getLoginContextProvider()
: provides the login contexts for the desired authentication mechanism.There also exists a utility class that allows to obtain different javax.security.auth.login.Configuration
for the most common setup [11]:
ConfigurationUtil#getDefaultConfiguration
: default OAK configuration supporting uid/pw login configures LoginModuleImpl
onlyConfigurationUtil#getJackrabbit2Configuration
: backwards compatible configuration that provides the functionality covered by jackrabbit-core DefaultLoginModule, namely:GuestLoginModule
: null login falls back to anonymousTokenLoginModule
: covers token based authenticationLoginModuleImpl
: covering regular uid/pw loginThe default security setup as present with Oak 1.0 is able to provide custom implementation on various levels:
AuthenticationConfiguration
implementations. In OSGi-base setup this is achieved by making the configuration a service. In a non-OSGi-base setup the custom configuration must be exposed by the SecurityProvider
implementation.import javax.security.auth.login.AppConfigurationEntry import javax.security.auth.login.Configuration; AppConfigurationEntry[] entries = new AppConfigurationEntry[]{new DefaultEntry(options)}; Configuration c = new Configuration() { @Override public AppConfigurationEntry[] getAppConfigurationEntry(String applicationName) { Map<String, ?> options = [....]; // choose control flag for custom login module (example here: REQUIRED) AppConfigurationEntry.LoginModuleControlFlag flag = LoginModuleControlFlag.SUFFICIENT; // create an entry for your custom login module AppConfigurationEntry customEntry = new AppConfigurationEntry("your.org.LoginModuleClassName", flag, options) // additionally use the oak default login module AppConfigurationEntry defaultEntry = new AppConfigurationEntry(("org.apache.jackrabbit.oak.security.authentication.user.LoginModuleImpl", LoginModuleControlFlag.REQUIRED, options) // define array of all entries in the correct order according to your needs return new AppConfigurationEntry[]{customEntry, defaultEntry}; } }; Configuration.setConfiguration(c);