A fairly common CAS deployment use case is to enforce access to a particular set of applications via user attributes and roles. Once the authentication/authorization server passed on the required attributes and entitlements to the application, each service might individually be tasked with controlling entry access, and once authorized, enforcement of a set of specific functions inside the application which the user may be allowed to carry out. The purpose of this tutorial is to present an alternative to the first scenario, by providing options to centrally control and manage that ruleset that allows the user to enter an application that is integrated with Apereo CAS.
Our task list is rather short:
To keep this tutorial simple, we are going to stick with the default CAS method of authentication with the obvious assumption that our authentication sources shall be different from sources that may produce user attributes.
5.2.x
Follow the instructions provided by the README
file to produce a functional build.
Attribute resolution strategies in CAS are controlled by the Person Directory project. The Person Directory dependency is automatically bundled with the CAS server and provides a number of options to fetch attributes and user data from sources such as LDAP, JDBC, etc. Since we do have multiple sources of attributes, the Person Directory component is also able to aggregate and merge the results and has options to decide how to deal with disagreements in case two sources produce conflicting data.
There is very little left for us to do other than to teach CAS about our specific data sources.
In the given cas.properties
file, the following settings allow us to fetch attributes from LDAP:
cas.authn.attribute-repository.ldap[0].baseDn=ou=people,dc=example,dc=org
cas.authn.attribute-repository.ldap[0].ldapUrl=ldap://localhost:1385
cas.authn.attribute-repository.ldap[0].userFilter=uid={0}
cas.authn.attribute-repository.ldap[0].useSsl=false
cas.authn.attribute-repository.ldap[0].bindDn=...
cas.authn.attribute-repository.ldap[0].bindCredential=...
cas.authn.attribute-repository.ldap[0].attributes.displayName=displayName
cas.authn.attribute-repository.ldap[0].attributes.givenName=givenName
cas.authn.attribute-repository.ldap[0].attributes.mail=email
The above configuration defined the very basic essentials as far as LDAP connection information while also teaching CAS the set of attributes that should be first retrieved and optionally remapped. In practice, CAS would begin to fetch displayName
, givenName
and mail
from the directory server and then process the final collection to include displayName
, givenName
and email
. From this point on, CAS only knows of the user’s email address under the email
attribute and needless to say, this is the attribute name that should be used everywhere else in the CAS configuration.
cas.some.setting[0]=value
. The index [0] is meant to be incremented by the adopter to allow for distinct multiple configuration blocks
The table table_users
in our HyperSQL database contains the user attributes we need:
uid |
attribute |
value |
---|---|---|
casuser |
role |
Manager |
casuser |
role |
Supervisor |
user2 |
role |
Engineer |
The above schema is what’s referred to as a Multi-Row setup in the Person Directory configuration. In other words, this is the sort of setup that has more than one row dedicated to a user entry and quite possibly similar to above, multiple rows carry out multiple values for a single attribute definition (i.e. role
). In order to teach CAS about this setup, we could start with the following settings:
cas.authn.attribute-repository.jdbc[0].attributes.role=personRole
cas.authn.attribute-repository.jdbc[0].singleRow=false
cas.authn.attribute-repository.jdbc[0].columnMappings.attribute=value
cas.authn.attribute-repository.jdbc[0].sql=SELECT * FROM table_users WHERE {0}
cas.authn.attribute-repository.jdbc[0].username=uid
cas.authn.attribute-repository.jdbc[0].driverClass=...
cas.authn.attribute-repository.jdbc[0].user=...
cas.authn.attribute-repository.jdbc[0].password=...
cas.authn.attribute-repository.jdbc[0].url=...
Pay attention to how the columnMappings
setting defines a set of 1-1 mappings between columns that contain the attribute name vs the attribute value. Furthermore and similar to the LDAP setup, we are teaching CAS to fetch the attribute role
(again, determined based on the mappings defined) and virtually rename the attribute to personRole
. Just like the LDAP setup and from this point on, CAS only knows of the user’s role under the personRole
attribute and needles to say, this is the attribute name that should be used everywhere else in the CAS configuration.
At this point, you should be able to authenticate into CAS and observe in the logs that the constructed authenticated principal contains the following attributes:
... <Authenticated principal [casuser] with attributes [{personRole=[Manager, Supervisor],
displayName=Test User, givenName=CAS, email=casuser@example.org}] ...>
If you need to troubleshoot, the best course of action would be to adjust logs to produce DEBUG
information.
The CAS service management facility allows CAS server administrators to declare and configure which services/applications may make use of CAS in different ways. The core component of the service management facility is the service registry that stores one or more registered services containing metadata that drives a number of CAS behaviors including authorization rules.
To keep this tutorial simple, we are going to use the JSON Service Registry. This registry reads services definitions from JSON configuration files on startup. JSON files are expected to be found inside a configured directory location and this registry will recursively look through the directory structure to find relevant JSON files.
For this tutorial, we expect CAS to find our JSON registration record files using the following setting:
cas.serviceRegistry.initFromJson=false
cas.serviceRegistry.json.location=file:/etc/cas/config/services
…and inside the above directory, we are going to create an ExampleApplication-100.json
file that contains the following:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://example\\.application\\.edu.*",
"name" : "ExampleApplication",
"id" : 100,
"evaluationOrder" : 1
}
All that remains for us is to decorate the registration record with the authorization rules.
The access strategy of a registered service provides fine-grained control over the application authorization rules. It describes whether the service is allowed to use the CAS server, allowed to participate in single sign-on authentication, and (as it’s relevant for our use case here) it may also be configured to require a certain set of attributes that must exist before access can be granted to the service.
Our JSON registration record could be modified as such:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "https://example\\.application\\.edu.*",
"name" : "ExampleApplication",
"id" : 100,
"evaluationOrder" : 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"personRole" : [ "java.util.HashSet", [ "Manager" ] ]
}
}
}
In simpler terms, the above configuration is saying: Access to applications that interact with CAS whose URL matches the pattern defined by the serviceId
is only granted if the authenticating user has an attribute personRole
that contains the value Manager
.
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 feel free to engage and contribute as best as you can.
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time