In the event that CAS is configured to delegate authentication to an external identity provider, it may be necessary to link the received profile from the identity provider to an internal account found in LDAP, SQL databases or any other systems. In this tutorial, we will focus on how to establish the authenticated subject based on this secondary lookup, using an identifier that is provided by the identity provider.
This tutorial specifically focuses on:
5.3.0-RC4
Our starting position is a CAS server that is configured to hand off the authentication flow to an external identity provider described here. Once the response from the identity provider is validated and a profile response has been collected, CAS will get access to a profile identifier plus a number of attributes that may have been released by the provider depending on the semantics of the protocol in question. In building the authenticated subject and linking that to the SSO session, CAS by default has options to either use what is called a typed id which is translated as [InternalProviderName]+[Separator]+[ProfileId]
or simply the profile id itself. This means that when the SSO session is established, the CAS authenticated subject will be communicated to all integrated applications using one of those two options.
Of course, you may run into scenarios where neither id would actually make that much sense. For example, the internal identity may be a seemly pseudorandom integer that would not be all that practical for other applications. The better scenario as an option would be for CAS to simply look up the real record associated with that internal id, (assuming linking between the two records is made available already), and build the principal identifier and attributes according to the data store internal to CAS.
This is tutorial on how to do just that.
Assuming delegated authentication is configured in CAS using any of the supported identity providers, our job here is put together code that massages the principal construction in CAS once the flow travels back from the identity provider over to CAS. To do this, we are going to take advantage of CAS PrincipalFactory
components whose job is to build authenticated subjects, or Principal
s. Most if not all of authentication strategies in CAS are preconfigured with their own instance of a PrincipalFactory
that would know how to translate a authenticated successful response into a Principal
object CAS can understand.
So start to prepare CAS with a customized configuration component that would house our specific choice of the PrincipalFactory
used in delegation scenarios. Once that is done, take note of the following bean definition posted in Pac4jAuthenticationEventExecutionPlanConfiguration
today:
@ConditionalOnMissingBean(name = "clientPrincipalFactory")
@Bean
public PrincipalFactory clientPrincipalFactory() {
return PrincipalFactoryUtils.newPrincipalFactory();
}
Note how the bean is marked as conditional, meaning it will only be used by CAS if an alternative definition by the same is not found. So, in order for CAS to pick up our own alternative implementation, we are going to provide that bean definition in our own configuration class as such:
@Bean
public PrincipalFactory clientPrincipalFactory() {
return PrincipalFactoryUtils.newPrincipalFactory();
}
Once you have the build compiling correctly, our next task would be to alter the body of our own clientPrincipalFactory
bean definition to do what it needs, which is the establishment of the CAS principal based on provided ids, attributes, etc. You can certainly provide your own implementation of PrincipalFactory
. What might be easier is if you were given the ability to change the implementation dynamically without having to rebuild CAS every time minor changes are required. To do this, aside from the default implementation of PrincipalFactory
, CAS provides a built-in option to externalize all that logic to a Groovy script. The construction of that option would more or less look like this:
@Autowired
private ResourceLoader resourceLoader;
@Bean
public PrincipalFactory clientPrincipalFactory() {
Resource script = resourceLoader.getResource("file:/etc/cas/config/CustomPrincipalFactory.groovy");
return PrincipalFactoryUtils.newGroovyPrincipalFactory(script);
}
…and of course, our Groovy script found at /etc/cas/config/CustomPrincipalFactory.groovy
would have the following structure:
import org.apereo.cas.authentication.principal.*
import org.apereo.cas.authentication.*
import org.apereo.cas.util.*
def run(Object[] args) {
def id = args[0]
def attributes = args[1]
def logger = args[2]
return new SimplePrincipal(id, attributes)
}
Now when CAS begins to construct the final authenticated principal, this Groovy script will be invoked to receive the identifier of the received response from the identity provider, any attributes that were submitted and extracted by CAS as a Map
as well as a convenient logger object. Next, you can code in additional logic to contact the necessary systems and execute queries based on the id to collect the real record linked to that id or any of the provided attributes and ultimately, return an object of type SimplePrincipal
that would carry the authenticated subject and its claims.
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.
Happy Coding,
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time