Virginia Tech
 | You can download a packaged set of changes from: cas-server-vt-mods.tar.gz
To see this in action:
NOTES:
- this will overwrite some files already provide by the standard CAS distribution
- there was one change to the CAS server source we made that is not required - it's just for logging purposes:
request.getSession(true).setAttribute("username", request.getParameter("username"));
|
Registered Services
What?
Registered Services allow some service-specific information to be included when going through the CAS system such as a service name (not just the sometimes unreadable service URL), an include file to display a custom login screen, and a URL to log out globally with. It is important to note that registered services are the basis for service includes as well as global logoff. Most importantly, registered services can disallow access to sites that have not been given the OK to use your service.
How?
Define .properties files.
Registered services are accomplished using a combination of JSP modifications and additional Java classes. Registered services consist of the following information defined in .properties files somewhere. By default these files live in WEB-INF/registered/. An example properties file is as follows:
# An example RegisteredService property file
edu.vt.ctu.cas.RegisteredService.logoutURL=https://my.service.edu/app/logoff
edu.vt.ctu.cas.RegisteredService.serviceInfoURL=https://my.service.edu/app/service.inc
edu.vt.ctu.cas.RegisteredService.serviceURL=https://my.service.edu/app/
edu.vt.ctu.cas.RegisteredService.editAccess=my-user-id
edu.vt.ctu.cas.RegisteredService.contactInfo=me@my-email-address.edu
edu.vt.ctu.cas.RegisteredService.serviceInfoCacheTime=36000000
edu.vt.ctu.cas.RegisteredService.serviceName=My Service
The fields are relatively straightforward:
- The logoutURL is where users are sent to log off upon global logoff.
- The serviceInfoURL is where to find their registered service include file.
- The serviceURL is the root URL to allow. This is the relavant setting for allowing access - more below.
- The editAccess is what user IDs can access this service info file (this is only here for future modifications).
- The contactInfo is what who to contact regarding this service (only used for information purposes).
- The serviceInfoCacheTime is how long in milliseconds to cache the service info include file.
- The serviceName is what to display to the user when this service is referenced.
 | I was experimenting with mapping Java Objects to properties files, which is why the property names are the fully-qualified class-name and member variable name, not something more readable. The files are generated directly from the Registered Service class, so an end user should never see one. The next incarnation of Registered services with CAS 3 will use a database backend. With CAS 2, load balancing is not supported very well, and reading properties files is faster than querying a remote database. This was done with the intention of writing an application to allow developers to register services through an automated web-based procedure. We decided to hold off on that until we move to CAS 3, however. |
Run the Registered Service Tool to generate the service properties file.
This tool will generate the registered service properties file. The following input would generate the previous example properties file. If you are using *NIX you can run the ./addService.sh script which will run the appropriate app:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
*** ***
*** VIRGINIA TECH CAS REGISTERED SERVICE TOOL. ***
*** PLEASE FOLLOW THE PROMPTS TO INPUT THE SERVICE INFORMATION. ***
*** ***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Enter value for: serviceName [default value: ]
Set serviceName (type: java.lang.String) : My Service
Enter value for: serviceURL [default value: ]
Set serviceURL (type: java.lang.String) : https://my.service.edu/app/
Enter value for: logoutURL [default value: ]
Set logoutURL (type: java.lang.String) : https://my.service.edu/app/logoff
Enter value for: serviceInfoURL [default value: ]
Set serviceInfoURL (type: java.lang.String) : https://my.service.edu/app/service.inc
Enter value for: serviceInfoCacheTime [default value: 36000000]
Set serviceInfoCacheTime (type: int) : 36000000
Enter value for: contactInfo [default value: ]
Set contactInfo (type: java.lang.String) : me@my-email-address.edu
Enter value for: editAccess [default value: ]
Set editAccess (type: java.lang.String) : my-user-id
Successfully created a new file: ./web/WEB-INF/registered/https%3A%2F%2Fmy.service.edu%2Fapp%2F
Use the backing Java classes to access services in your JSP.
The Java classes required for registered services are:
edu.vt.ctu.cas.RegisteredService
edu.vt.ctu.cas.RegisteredServiceCache
edu.vt.ctu.cas.RegisteredServiceFilter
edu.vt.ctu.cas.RegisteredServiceTool
The Registered Service class is a representation of a registered service.
The Registered Service Filter provides a Filename Filter to find the registered services' properties files.
The Registered Service Tool offers a command-line utility to generate registered service property files and to load them from the filesystem.
The Registered Service Cache class does the grunt work of caching and retrieving the registered service objects. This is what the JSP pages interact with.
To disallow access, we added the following code to the 3 JSP files - login.jsp, goService.jsp, and redirect.jsp. I think the handling was slightly different for each file, which is why we did not create a seperate JSP for it.
<%
String service = request.getParameter("service");
if(service == null) {
String primaryService = "logon?service=https:;
response.encodeRedirectURL(primaryService);
response.sendRedirect(primaryService);
return;
}
String fileSep = System.getProperty("file.separator");
String path = getServletContext().getRealPath(fileSep);
String webinfPath = path + "WEB-INF" + fileSep;
String serverScheme = request.getScheme();
String serverName = request.getServerName();
int serverPort = request.getServerPort();
String servletPath = request.getContextPath();
log.debug("Using registered dir: " + webinfPath + "registered" + fileSep);
RegisteredService registered = RegisteredServiceCache.getRegisteredService(webinfPath +
"registered" + fileSep, service);
if(registered == null) {
%>
<%@ include file="invalidService.jsp" %>
<%
return;
}
%>
We simply give the path to the registered service includes and pass the service URL. The Registered Service Cache is a singleton class which matches a registered service entry to the provided service.
 | The services are matched using the java.lang.String beginsWith() function. For example:
A registered service with https://my.service.edu/app/ will match anything that begins with that string, such as: https://my.service.edu/app/doActions.jsp or https://my.service.edu/app/images/authenticated/my-dog.png. It would not match: https://my.service.edu/app2/ or https://my.service.edu/. This way we can offer direct linking to any URL beyond the base service URL which CAS will allow and disallow other services. It is possible in the future we will want to make this more granular, but for now this met our needs. |
Service Information Include Files
What?
A Service Information Include file is an HTML snippet that services may include for display on the central login page. This way a service may brand their login page while still maintaing a common look-and-feel to tell the user that they are using the Central Authentication Service.
 | Example My VT include file
The grayed-out section is where the My VT include is shown, and what will be replaced by other include files. This gives services a good way to brand their own service when logging in. User education is necessary to inform the user that they are actually logging in to the university, not just the service shown.
|
How?
The service information include files are dependent on the services being registered so a unique URL may be specified where to find the service information. This required modifications to the JSP files and additional Java classes.
In addition to the Registered services being present, service info is handled by one additional Java class:
edu.vt.ctu.cas.ServiceInfoCache
Following the validation of the requested service, the login.jsp includes:
int cacheTime = registered.getServiceInfoCacheTime();
String serviceInfoUrl = registered.getServiceInfoURL();
if(serviceInfoUrl == null) {
serviceInfoUrl = serverScheme + ": + serverName + ":" + serverPort + servletPath +
"/includes/default.inc";
}
try {
serviceInfoUrl = java.net.URLDecoder.decode(serviceInfoUrl, "UTF-8");
} catch(java.io.UnsupportedEncodingException e) {}
String serviceName = registered.getServiceName();
if(serviceName == null) {
serviceName = registered.getServiceURL();
}
String serviceInfo = ServiceInfoCache.getServiceInfo(serviceInfoUrl, webinfPath + "cached" +
fileSep, registered.getServiceInfoCacheTime());
if(serviceInfo == null) {
serviceInfo = ServiceInfoCache.getServiceInfo(serverScheme + ": + serverName + ":" +
serverPort + servletPath + "/includes/default.inc", webinfPath + "cached" + fileSep, 36000000);
}
Then later, where the service info is included:
<div id="service-info">
<%=serviceInfo%>
</div>
 | The service info cache uses EhCache to maintain the registered services efficiently in memory. In addition to service includes, we use the service info cache to for obtaining an alerts file. If the file exists, there is a pressing alert to display to the students such as classes being cancelled or a tornado warning, we will display that above all service include files. |
Global Logoff
What?
Global logoff allows users to go to http://my.cas.server.edu/logoff and log out of all the services they signed on to through CAS.
 | Example logoff
The logout informs the user which systems they are being signed out of. If the browser does not support inline frames, they are presented with logout links for each service. We set the session timeout to 2 hours for the CAS server. This is well above the timeouts for any one of our services. We don't anticipate users will spend more than 2 hours at any one Virginia Tech site.
|
How?
This is also dependent on services being registered so each service can specify a logout link. The only modifications apart from what was required for registered services are in JSP files. This eliminates the need for registered services to use a modified CAS client in order to provide logout functionality - the user generally just needs to be sent to the normal logout URL for the service. The global logoff is not perfect, but should work fine in nearly all circumstances. When a user is redirected to a service - either through goService.jsp or redirect.jsp, the Servicenfo object is stored in their session. We modified warnService.jsp to redirect to redirect.jsp.
java.util.TreeMap logouts = (java.util.TreeMap)request.getSession().getAttribute("LOGOUTS");
if(logouts == null) {
logouts = new java.util.TreeMap();
request.getSession().setAttribute("LOGOUTS", logouts);
}
if(!logouts.containsKey(registered.getLogoutURL())) {
logouts.put(registered.getLogoutURL(), registered);
}
Then the logout.jsp was modified to read all the logout links and output inline frames with no height, so the user's browser actually hits the pages and logs off normally.
<%
if(logouts != null) {
java.util.Iterator i = logouts.keySet().iterator();
while(i.hasNext()) {
Object key = i.next();
RegisteredService service = (RegisteredService)logouts.get(key);
String logoutURL = service.getLogoutURL();
String serviceName = service.getServiceName();
String serviceURL = service.getServiceURL();
%>
<li><%= serviceName %> <iframe height='0px' width='0px' style='visibility: hidden; border:
0px; margin: 0px; width: 0px; height: 0px;' src='<%= logoutURL %>'>(<a
href='<%= logoutURL %>' target='_blank'>Click here to log out of <%= serviceName
%> manually.</a>)</iframe></li>
<%
}
}
%>
If you persist user cookies longer than a session (e.g. by Adjusting the CAS SSO duration "remember me" and/or by Serializing CAS 2 Ticket Caches through server restarts) then the global logoff code needs to be modified. A solution that worked for me was to store the logout Map in the TicketGrantingTicket.
I have attached store_logouts_in_TicketGrantingTicket.patchwhich is my patch to the VT global logoff code. Instead of storing the logout Map in the user session, it stores it in the TicketGrantingTicket which gets stored in the tgcCache (which lasts longer than the session). (Note that I have made several other modifications to my CAS instance, so my patch may not automatically apply cleanly.)
cas-server-vt-mods.tar.gz (attached as part of the article) contains only some classfiles (all part of the ehcache package) Where can I find the patch that contains the changes mentioned in this article..
Thanks!