The ability to fetch attributes from external data stores has been present in CAS since the days of 3.x
. This functionality was and, to this day, is provided by an Apereo project called Person Directory which is a Java framework for resolving persons and attributes from a variety of underlying sources. It consists of a collection of components that retrieve, cache, resolve, aggregate and merge person attributes from JDBC, LDAP and more. CAS attempts to take advantage of this framework through a concept called PrincipalResolver
whose goal is to construct a final identifiable authenticated principal for CAS which carries a number of attributes inside it fetched from attribute repository sources. This meant that for instance, one could authenticate with LDAP in one query and then turn around the ask the same LDAP, a relational database and a Groovy script to fetch attributes for the resolved principal and combine all results into a final collection.
Note that in most cases, and starting around CAS 4.x
, the authentication engine has been enhanced to be able to retrieve and resolve attributes from the authentication source, which would eliminate the need for configuring a separate attribute repository especially if both the authentication and the attribute source are the same. Using separate resolvers and sources should only be required when sources are different, or when there is a need to tackle more advanced attribute resolution use cases such as cascading, merging, etc.
PrincipalResolver
components and Person Directory's attribute repositories shall always execute, if and when configured, regardless of how the authentication event occurs. Whether the user is authenticating against a CAS-owned account store or is handed off to an external identity provider, in either scenario CAS is able to put together attributes from both the authentication source as well as any and all attribute repositories configured.
This tutorial focuses on a number of use case involving attribute repositories, fetching attributes from an external store a JSON file and a collection of hard-coded stubbed attributes. We will demonstrate variations on how attribute sources may be cached, filtered and released in a number of fancy ways.
Our starting position is based on:
6.1.x
11
casuser
and Mellon
via static authentication.Our JSON attribute repository source, separate from the CAS authentication store is fairly simple:
{
"casuser": {
"firstName": ["Bob"],
"employeeNumber": ["123456"],
"lastName": ["Johnson"]
}
}
Our external attribute repositories are then taught to CAS:
cas.authn.attribute-repository.json[0].location=file://etc/cas/config/attribute-repository.json
cas.authn.attribute-repository.json[0].id=MyJson
cas.authn.attribute-repository.stub.id=StaticStub
cas.authn.attribute-repository.stub.attributes.uid=mmoayyed
cas.authn.attribute-repository.stub.attributes.displayName=Misagh Moayyed
cas.authn.attribute-repository.stub.attributes.firstName=Misagh
cas.authn.attribute-repository.stub.attributes.lastName=Moayyed
Note that each attribute repository is given an id
which can the be used as a filter to narrow the resolution logic down to matching repositories.
The requirements for this use case are as follows:
MyJson
attribute repository.The relevant properties for this use case are:
cas.authn.attribute-repository.expirationTime=0
cas.authn.attribute-repository.expirationTimeUnit=seconds
cas.authn.attribute-repository.merger=multivalued
cas.personDirectory.attributeResolutionEnabled=false
Our service definition matches the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ExampleApplication",
"id" : 1,
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository",
"attributeRepositoryIds": ["java.util.HashSet", [ "myjson" ]]
}
}
}
Since caching is disabled, you can change the underlying attribute value in the JSON attribute repository and CAS should pick up the change and release the new attribute value to the example application.
The requirements for this use case are as follows:
MyJson
attribute repository.60
seconds.The relevant properties for this use case are:
cas.authn.attribute-repository.expirationTime=60
cas.authn.attribute-repository.expirationTimeUnit=seconds
cas.authn.attribute-repository.merger=multivalued
cas.personDirectory.attributeResolutionEnabled=false
Our service definition matches the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ExampleApplication",
"id" : 1,
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository",
"attributeRepositoryIds": ["java.util.HashSet", [ "myjson" ]]
}
}
}
Since caching is disabled, you can change the underlying attribute value in the JSON attribute repository
and CAS should pick up the change and release the new attribute value to the example application in about 60
seconds.
The requirements for this use case are as follows:
MyJson
attribute repository.5
seconds and service-level caching for 30
seconds.The relevant properties for this use case are:
cas.authn.attribute-repository.expirationTime=5
cas.authn.attribute-repository.expirationTimeUnit=seconds
cas.authn.attribute-repository.merger=multivalued
cas.personDirectory.attributeResolutionEnabled=false
Our service definition matches the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ExampleApplication",
"id" : 1,
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.cache.CachingPrincipalAttributesRepository",
"duration" : {
"@class" : "javax.cache.expiry.Duration",
"timeUnit" : [ "java.util.concurrent.TimeUnit", "SECONDS" ],
"expiration" : 30
},
"attributeRepositoryIds": ["java.util.HashSet", [ "myjson" ]]
}
}
}
Attributes fetched and released for this example application may be updated after 30
seconds, while the global cache attempts to expire and resolve attributes for other applications after 5
seconds.
The requirements for this use case are as follows:
StaticStub
attribute repository.MyJson
attribute repository.30
seconds.The relevant properties for this use case are:
cas.authn.attribute-repository.expirationTime=30
cas.authn.attribute-repository.expirationTimeUnit=seconds
cas.authn.attribute-repository.merger=multivalued
cas.personDirectory.attributeResolutionEnabled=true
cas.personDirectory.activeAttributeRepositoryIds=StaticStub
Our service definition matches the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ExampleApplication",
"id" : 1,
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository",
"attributeRepositoryIds": ["java.util.HashSet", [ "myjson" ]],
"mergingStrategy" : "MULTIVALUED"
}
}
}
The requirements for this use case are identical to the one above. The only difference is, we are going to ignore attributes resolved at authentication time from our StaticSub
attribute repository for the example application and only hit our selected attribute repository, MyJson
.
Our service definition matches the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ExampleApplication",
"id" : 1,
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.DefaultPrincipalAttributesRepository",
"attributeRepositoryIds": ["java.util.HashSet", [ "myjson" ]],
"ignoreResolvedAttributes": true,
"mergingStrategy" : "MULTIVALUED"
}
}
}
The requirements for this use case are as follows:
StaticStub
attribute repository.MyJson
attribute repository.30
seconds, and service-level caching for 30
minutes.The relevant properties for this use case are:
cas.authn.attribute-repository.expirationTime=10
cas.authn.attribute-repository.expirationTimeUnit=seconds
cas.authn.attribute-repository.merger=multivalued
cas.personDirectory.attributeResolutionEnabled=true
cas.personDirectory.activeAttributeRepositoryIds=StaticStub
Our service definition matches the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://app.example.org",
"name" : "ExampleApplication",
"id" : 1,
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
"principalAttributesRepository" : {
"@class" : "org.apereo.cas.authentication.principal.cache.CachingPrincipalAttributesRepository",
"duration" : {
"@class" : "javax.cache.expiry.Duration",
"timeUnit" : [ "java.util.concurrent.TimeUnit", "MINUTES" ],
"expiration" : 30
},
"attributeRepositoryIds": ["java.util.HashSet", [ "myjson" ]]
}
}
}
I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please know that all other use cases, scenarios, features and theories certainly are possible as well. Feel free to engage and contribute as best as you can.
Happy Coding,
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time