Token Management : The Default Implementation
General Notes
The default implementation of the token management API stores login tokens along with the user's home directory in the repository. Along with the hash of the login token separated properties defining the expiration time of the token as well as as additional properties associated with the login tokens. This additional information may be mandatory (thus validated during the login) or optional. The optional properties are meant to have informative value only and will be transferred to public attributes as exposed by the AuthInfo present with each ContentSession.
Token Management Operations
Token Creation
The creation of a new token is triggered by valid and supported Credentials
passed to the login module chain that contain an additional, empty .token
attribute.
The TokenLoginModule will obtain these Credentials
from the shared state
during the commit phase (i.e. phase 2 of the JAAS authentication) and will pass
them to the configured TokenProvider implementation the following sequence:
Credentials shared = getSharedCredentials();
if (shared != null && tokenProvider.doCreateToken(shared)) {
[...]
TokenInfo ti = tokenProvider.createToken(shared);
[...]
}
In case of success these steps will have generated a new token and stored it's hash along with all mandatory and informative attributes to the new content node representing the token.
Supported Credentials for Token Creation
By default the implementation deals with shared SimpleCredentials
.
With Oak 1.5.8 the token management has been extended in order to allow
for custom Credentials
implementations. This is achieved by registering
a custom implementation of the CredentialsSupport interface.
The default the token management uses SimpleCredentialsSupport.
See also OAK-4129 and section Pluggability below) for additional information.
Token Validation
Once a token has been created it can be used for subsequent repository logins with TokenCredentials. This time the TokenLoginModule will attempt to perform the login phase (i.e. phase 1 of the JAAS authentication).
This includes resolving the login token (TokenProvider.getTokenInfo
) and
asserting it's validity in case it exists. The validation consists of following steps:
- check that the token has not expired (
TokenInfo.isExpired
) - verify that all mandatory attributes are present and match the expectations (
TokenInfo.matches
)
Only if these steps have been successfully completed the login of the TokenLoginModule will succeed.
Token Removal
A given login token (and the node associated with it) will be removed if
the authentication fails due to an expired token or with an explicit API call
i.e. TokenInfo.remove
.
Resetting Expiration Time
The default TokenProvider
implementation will automatically reset the expiration
time of a given token upon successful authentication.
This behavior can be disabled by setting the tokenRefresh
configuration parameter
to false
(see PARAM_TOKEN_REFRESH
below). In this case expiration time will
not be reset and an attempt to do so using the API (e.g. calling TokenInfo.resetExpiration(long loginTime)
) will return false
indicating
that the expiration time has not been reset. The token will consequently expire
and the user will need to login again using the configured login
mechanism (e.g. using the credentials support for token creation).
Token Cleanup
Automatic token cleanup can be enabled by setting the tokenCleanupThreshold
parameter
to a value larger than 0
(0
means disabled). This will trigger a cleanup call if
the number of tokens under a user exceeds this value. (As an implementation detail a
throttling method was introduced to only allow the call to go through 1/8 times).
This is available with Oak 1.7.12 on, see also [OAK-6818]for additional information.
Representation in the Repository
Content Structure
The login tokens issued for a given user are all located underneath a node
named .tokens
that will be created by the TokenProvider
once the first token
is created. The default implementation creates a distinct node for each login
token as described below
testUser {
"jcr:primaryType": "rep:User",
...
".tokens" {
"jcr:primaryType": "rep:Unstructured",
"2014-04-10T16.09.07.159+02.00" {
"jcr:primaryType": "rep:Token",
...
"2014-05-07T12.08.57.683+02.00" {
"jcr:primaryType": "rep:Token",
...
}
"2014-06-25T16.00.13.018+02.00" {
"jcr:primaryType": "rep:Token",
...
}
}
}
Token Nodes
As of Oak 1.0 the login token are represented in the repository as follows:
- the token node is referenceable with the dedicated node type
rep:Token
(used to be unstructured in Jackrabbit 2.x) - expiration and key properties are defined to be mandatory and protected
- expiration time is obtained from
PARAM_TOKEN_EXPIRATION
specified in the login attributes and falls back to the configuration parameter with the same name as specified in the configuration options of theTokenConfiguration
.
The definition of the new built-in node type rep:Token
:
[rep:Token] > mix:referenceable
- rep:token.key (STRING) protected mandatory
- rep:token.exp (DATE) protected mandatory
- * (UNDEFINED) protected
- * (UNDEFINED) multiple protected
The following example illustrates the token nodes resulting from this node type definition:
testUser {
"jcr:primaryType": "rep:User",
...
".tokens" {
"2014-04-10T16.09.07.159+02.00" {
"jcr:primaryType": "rep:Token",
"jcr:uuid": "30c1f361-35a2-421a-9ebc-c781eb8a08f0",
"rep:token.key": "{SHA-256}afaf64dba5d862f9-1000-3e2d4e58ac16189b9f2ac95d8d5b692e61cb06db437bcd9be5c10bdf3792356a",
"rep:token.exp": "2014-04-11T04:09:07.159+02:00",
".token.ip": "0:0:0:0:0:0:0:1%0"
".token.otherMandatoryProperty": "expectedValue",
"referer": "http://localhost:4502/crx/explorer/login.jsp"
"otherInformalProperty": "somevalue"
},
"2014-05-07T12.08.57.683+02.00" {
"jcr:primaryType": "rep:Token",
"jcr:uuid": "c95c91e2-2e08-48ab-93db-6e7c8cdd6469",
"rep:token.key": "{SHA-256}b1d268c55abda258-1000-62e4c368972260576d37e6ba14a10f9f02897e42992624890e22c522220f7e54",
"rep:token.exp": "2014-05-08T00:08:57.683+02:00"
},
...
}
}
}
Validation
The consistency of this content structure both on creation and modification is
asserted by a dedicated TokenValidator
. The corresponding errors are
all of type Constraint
with the following codes:
Code | Message |
---|---|
0060 | Attempt to create reserved token property in other ctx |
0061 | Attempt to change existing token key |
0062 | Change primary type of existing node to rep:Token |
0063 | Creation/Manipulation of tokens without using provider |
0064 | Create a token outside of configured scope |
0065 | Invalid location of token node |
0066 | Invalid token key |
0067 | Mandatory token expiration missing |
0068 | Invalid location of .tokens node |
0069 | Change type of .tokens parent node |
Configuration
The default Oak TokenConfiguration
allows to define the following configuration
options for the TokenProvider
:
Configuration Parameters
Parameter | Type | Default |
---|---|---|
PARAM_TOKEN_EXPIRATION | long | 2 * 3600 * 1000 (2 hours) |
PARAM_TOKEN_LENGTH | int | 8 |
PARAM_TOKEN_REFRESH | boolean | true |
PARAM_PASSWORD_HASH_ALGORITHM | String | SHA-256 |
PARAM_PASSWORD_HASH_ITERATIONS | int | 1000 |
PARAM_PASSWORD_SALT_SIZE | int | 8 |
PARAM_TOKEN_CLEANUP_THRESHOLD | long | 0 (no cleanup) |
Pluggability
In an OSGi-based setup the default TokenConfiguration
you can bind a
custom implementation of the CredentialsSupport interface. Doing so
allows to support any type of custom credentials, which do not reveal
the ID of the user logging into repository.
In particular when chaining the TokenLoginModule
and the ExternalLoginModule
the CredentialsSupport can be used to authenticate and synchronize
users provided by third party systems during phase 1 (login) and generate
a login token during phase 2 (commit). See section
Authentication with the External Login Module
for additional details. For this to work the same CredentialsSupport
must be configured with the ExternalIdentityProvider and the TokenConfiguration
and CredentialsSupport.getUserId
must reveal the ID of the synced user (i.e. ExternalUser.getId
).
In general the following steps are required in order to plug a different CredentialsSupport
into the default TokenConfiguration
:
- implement the
CredentialsSupport
interface (e.g. as extension to theExternalIdentityProvider
) - make sure the implementation is an OSGi service and deploy it to the Oak repository.
Examples
Example CredentialsSupport
In an OSGi-based setup it's sufficient to make the service available to the repository
in order to enable a custom CredentialsSupport
.
@Component
@Service(value = {CredentialsSupport.class})
/**
* Custom implementation of the {@code CredentialsSupport} interface.
*/
final class MyCredentialsSupport implements CredentialsSupport {
@Nonnull
@Override
public Set<Class> getCredentialClasses() {
return ImmutableSet.<Class>of(MyCredentials.class);
}
@CheckForNull
@Override
public String getUserId(@Nonnull Credentials credentials) {
if (credentials instanceof MyCredentials) {
// TODO: resolve user id
return resolveUserId(credentials);
} else {
return null;
}
}
@Nonnull
@Override
public Map<String, ?> getAttributes(@Nonnull Credentials credentials) {
// TODO: optional implementation
return ImmutableMap.of();
}
@Override
public boolean setAttributes(@Nonnull Credentials credentials, @Nonnull Map<String, ?> attributes) {
// TODO: optional implementation
return false;
}
[...]
}
Example CredentialsSupport in Combination with External Authentication
See section Authentication with the External Login Module for an example.