I collaborated on an interesting CAS use case that asked for the following behavior:
On successful login attempts, could we display a list of applications and services, registered with CAS, for which the authenticated user is authorized for access?
Sure, we could. In this post, I will review the steps required to handle this use case at a high level while also briefly reviewing access and authorization strategies with CAS registered applications using modest attribute-based access control (ABAC).
Our starting position is as follows:
6.2.x
11
By authorized for access, the question refers to the CAS concept of Access Strategy for registered applications. This strategy provides fine-grained control over the service authorization rules and can be configured to require a certain set of attributes that must exist before access can be granted to the service. This behavior allows one to configure various attributes in terms of access roles for the application and define rules that would be enacted and validated when an authentication request from the application arrives.
For example, the following policy allows access to https://example.com
only if the authenticated user carries a cn
attribute that contains admin
as a value:
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "https://example.com",
"name": "Sample",
"id": 1,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"requiredAttributes" : {
"@class" : "java.util.HashMap",
"cn" : [ "java.util.HashSet", [ "admin" ] ]
}
}
}
Such applications are managed and tracked by CAS using its own service registry. So the task at hand requires that on successful authentication attempts, the system should begin to execute the following steps:
All of the above can be done at the right entry point by extending the CAS Spring Webflow to override the GenericSuccessViewAction
that is responsible for executing and rendering the view after successful attempts.
We start with extending the CAS configuration so that we can inject our own custom implementation of the GenericSuccessViewAction
components:
@Bean
public Action genericSuccessViewAction() {
return new CustomGenericSuccessViewAction(...);
}
Our CustomGenericSuccessViewAction
will run through the above steps as such:
public class CustomGenericSuccessViewAction extends GenericSuccessViewAction {
public CustomGenericSuccessViewAction(...) {}
@Override
protected void doPostExecute(final RequestContext requestContext) throws Exception {
var authorizedServices = new ArrayList<RegisteredService>();
var tgt = WebUtils.getTicketGrantingTicketId(requestContext);
getAuthentication(tgt).ifPresent(authentication -> {
/**
* Step 1: Collect all services
*/
servicesManager.getAllServices().forEach(registeredService -> {
/**
* Step 2: Filter/Collect services per authorization rules
*/
if (RegisteredServiceAuthorizer.isAuthorized(registeredService, authentication)) {
authorizedServices.add(registeredService);
}
});
});
/**
* Step 3: Pass the final result to the view layer
*/
requestContext.getFlowScope()
.put("authorizedServices", authorizedServices);
super.doPostExecute(requestContext);
}
}
The bulk of authorization work is done by our RegisteredServiceAuthorizer#isAuthorized()
component which more or less does the following:
try {
RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(registeredService);
val service = new WebApplicationServiceFactory().createService(registeredService.getServiceId());
RegisteredServiceAccessStrategyUtils.ensurePrincipalAccessIsAllowedForService(
service, registeredService, authentication);
return true;
} catch (final Exception e) {
log.trace(e.getMessage());
}
return false;
To display our list of authorized applications in a mini-portal fashion, we should begin by customizing casGenericSuccessView.html
file that is rendered on successful authentication attempts. The page should have access to the authorizedServices
flow scope variable, and can loop through the results to display the applications:
You have access to the following applications:
<ul>
<li th:each="service : ${authorizedServices}">
<span th:utext="${service.name}"></span>
</li>
</ul>
This capability is automatically provided by CAS 6.3.x
, so you can stop writing code.
If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to send us a note and ask about consulting and support services.
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