1. Dan Ellentuck, Columbia University
Bill Thompson, Unicon Inc.
June 10-15, 2012
Growing Community;
Growing Possibilities
2. Reasons to Choose CAS:
Google Apps SSO
SAML Support
Vendor Support
Community Support
Tie-in with other open source tools and products, e.g.,
Sakai
Complicating Factors:
Pre-existing local web auth system
Active, diverse client base
Question:
How can legacy system be migrated to CAS?
3. CAS support for Google Apps SSO
Migrating a pre-existing web auth system to
CAS
CAS customizations and enhancements:
• Adding support for a new protocol
• Plugging in a custom service registry
• Enabling per-service UI tweaks
• Changing some basic login behavior
4. Google Apps SSO is based on SAML 2. See:
https://developers.google.com/google-
apps/sso/saml_reference_implementation
Step-by-step instructions on configuring CAS for Google
Apps sso:
https://wiki.jasig.org/pages/viewpage.action?pageId=60634
84
Works OOTB.
5. Sibling of CAS, called “WIND”.
Cookie-based SSO.
No generic login.
Per-service UI customization and opt-in SSO.
Similar APIs with different request param names:
CAS:
/login?service=https://MY-APPLICATION-PATH
/logout
/serviceValidate?service=https://APPLICATION-PATH&ticket=SERVICE-TICKET
WIND:
/login?destination=https://MY-APPLICATION-PATH
/logout
/validate?ticketid=SERVICE-TICKET
7. Service registry with maintenance UI
Service attributes for UI customization, multiple destinations,
attribute release, application contacts, etc.
SERVICE DESTINATION
SERVICE_LABEL
SERVICE_LABEL
DESTINATION
SINGLE_SIGN_ON (T/F)
PROXY_GRANTING (T/F)
RETURN_XML (T/F) SERVICE_CONTACT
ID_FORMAT
DESCRIPTION SERVICE_LABEL
HELP_URI (for customizing UI) EMAIL_ADDRESS
IMAGE_PATH(for customizing UI ) CONTACT_TYPE
HELP_LABEL(for customizing UI)
AFFILIATION
SERVICE_LABEL
AFFILIATION (like ATTRIBUTE)
8. Collaboration between Columbia and Unicon.
Tasks:
◦ Plug legacy service registry into CAS.
◦ Add legacy authentication protocol to CAS.
◦ Port login UI customizations to CAS.
◦ Change some login behavior (eliminate generic login.)
New service registrations must use CAS protocol.
Existing clients can use either legacy or CAS protocols
during transition.
9. • Java
• View technologies (JSP, CSS, etc.)
• Maven (dependencies; overlays)
• Spring configuration (CAS set up)
• Spring Web Flow (SWF)
• App server/web server (tomcat/apache)
10. Service Registry is obvious extension point.
Advantages to plugging in local service
registry:
◦ Retain extended service attributes and functions
◦ Remove migration headache
◦ Can continue to use legacy maintenance UI
11. Step 1: Write a CAS RegisteredService adaptor, part 1.
Write an interface that extends CAS RegisteredService with
any extra attributes in the custom service registry.
public interface WindRegisteredService extends RegisteredService {
/**
* Returns a display label for the help link. Can be null.
* Ignored if getHelpUri() is null.
* @return String
*/
String getHelpLabel();
/**
* Returns a help URI. Can be null.
* @return String
*/
String getHelpUri();
...etc.
}
12. Step 2: Write a CAS RegisteredService adaptor, part 2. Write a
RegisteredService implementation that adapts an instance of the
custom service to the extended RegisteredService interface.
public class WindRegisteredServiceImpl implements WindRegisteredService,
Comparable<RegisteredService> {
public boolean matches(Service targetService) {
if (!isEnabled() || targetService == null ||
targetService.getId() == null || targetService.getId().isEmpty())
return false;
for (String registeredDestination :
List<String>) getWindService().getAllowed_destinations()) {
String target = targetService.getId().substring(0,
registeredDestination.length());
if (registeredDestination.equalsIgnoreCase(target))
return true;
}
return false;
}
...
}
13. Step 3: Implement a CAS ServicesManager (maps incoming
Service URL of a request with the matching CAS
RegisteredService.)
public class ReadOnlyWindServicesManagerImpl implements ReloadableServicesManager
{
...
public RegisteredService findServiceBy(Service targetService) {
edu.columbia.acis.rad.wind.model.Service windService =
findWindService(targetService);
return ( windService != null )
? getRegisteredServicesByName().get(windService.getLabel())
: null;
}
public RegisteredService findServiceBy(long id) {
return getRegisteredServicesById().get(id);
}
...
}
14. Step 4: Write Spring bean definitions for the new
ServicesManager.
applicationContext.xml
<!–
Default servicesManager bean definition replaced by custom servicesManager
<bean
id="servicesManager"
class="org.jasig.cas.services.DefaultServicesManagerImpl">
<constructor-arg index="0" ref="serviceRegistryDao"/>
</bean>
-->
<bean
id="servicesManager"
class="edu.columbia.acis.rad.wind.cas.ReadOnlyWindServicesManagerImpl">
<constructor-arg index=“0” ref =“wind-ServicesCollection"/>
</bean>
...etc.
15. Result…
Additional service attributes and functions are
available to CAS
Custom maintenance UI can be used
Service registry uses custom logic to match
Service URL of incoming request with appropriate
registered service.
Easy migration
16. CAS is multi-protocol
Wind and CAS protocols are similar but not
identical
Different servlet API and validation response
formats
Advantages to adding legacy protocol to CAS:
◦ Single authentication service
◦ Single SSO domain
◦ Easy migration from legacy system
17. Step 1: Implement the CAS Service interface for the new
protocol by subclassing abstractWebApplicationService:
public class WindService extends AbstractWebApplicationService {
private static final String DESTINATION_PARAM = "destination";
private static final String SERVICE_PARAM = "service";
private static final String TICKET_PARAM = "ticketid";
...
// Create a Service instance from the request:
public static WindService from(HttpServletRequest request, HttpClient httpClient)
{
String origUrl = request.getParameter(DESTINATION_PARAM);
...
new WindService(origUrl, origUrl, /*artifactId not used*/ null, httpClient);
}
18. Step 2: Write an ArgumentExtractor class to retrieve values
of protocol-specific request parameters and return
instances of the Service class created in Step 1:
public class WindArgumentExtractor extends AbstractSingleSignOutEnabledArgumentExtractor
{
private static final String TICKET_PARAM = "ticketid";
...
protected WebApplicationService extractServiceInternal
( HttpServletRequest request)
//Coming in from validation request
if ("/validate".equals(request.getServletPath())) {
String ticketId = request.getParameter(TICKET_PARAM);
ServiceTicket st = (ServiceTicket)
this.ticketRegistry.getTicket(ticketId, ServiceTicket.class);
WindService ws = st != null ? (WindService) st.getService() : null;
...
return WindService.from(ticketId, ws., getHttpClientIfSingleSignOutEnabled());
19. Step 3: In web.xml, map the servlet path for the
protocol’s version of the service ticket validation
request to the cas servlet:
<servlet>
<servlet-name>cas</servlet-name>
<servlet-class>
org.jasig.cas.web.init.SafeDispatcherServlet
</servlet-class>
<init-param>
<param-name>publishContext</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
...
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/validate</url-pattern>
</servlet-mapping>
...
20. Step 4: Write a view class to format the service ticket
validation response:
class WindResponseView extends AbstractCasView {
....
private buildSuccessXmlResponse(Assertion assertion) {
def auth = assertion.chainedAuthentications[0]
def principalId = auth.principal.id
def xmlOutput = new StreamingMarkupBuilder()
xmlOutput.bind {
mkp.declareNamespace('wind': WIND_XML_NAMESPACE)
wind.serviceResponse {
wind.authenticationSuccess {
wind.user(principalId)
wind.passwordtyped(assertion.fromNewLogin)
wind.logintime(auth.authenticatedDate.time)
...etc.
}
}
}.toString()
}
21. Step 5: Define and wire up beans for the various
protocol operations:
argumentExtractorsConfiguration.xml
defines ArgumentExtractor classes for the various supported protocols:
<bean id="windArgumentExtractor"
class="edu.columbia.cas.wind.WindArgumentExtractor"
p:httpClient-ref="httpClient"
p:disableSingleSignOut="true">
<constructor-arg index="0" ref="ticketRegistry"/>
</bean>
uniqueIdGenerators.xml
protocol is mapped to uniqueID generator for service tickets via Service class:
<util:map id=“uniqueIdGeneratorsMap”>
<entry key=“edu.columbia.cas.wind.WindService”
value-ref=“serviceTicketUniqueIdGenerator” />
...etc.
</util:map>
22. Step 5: Define and wire up beans for the various protocol
operations (cont’d):
cas-servlet.xml
bean definitions made available to the web flow:
<prop
key=“/validate”>
windValidateController
</prop
...
<bean id=“windValidateController”
class=“org.jasig.cas.web.ServiceValidateController”
p:proxyHandler-ref=“proxy20Handler”
p:successView=“windServiceSuccessView”
p:failureView=“windServiceFailureView”
p:validationSpecificationClass=
“org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification”
p:centralAuthenticationService-ref=“centralAuthenticationService”
p:argumentExtractor-ref=“windArgumentExtractor”/>
...etc.
24. Result…
CAS will detect a request in the new protocol;
Extract appropriate request parameters;
Respond in the appropriate format.
Legacy clients continue to use usual auth protocol
until ready to migrate.
Single server/SSO realm.
25. Adding local images and content to the CAS login UI is a
common implementation step.
CAS lets each RegisteredService have its own style sheet (high
effort.)
Legacy auth service allows per-service tweaks to the login UI
(low effort):
• Custom logo
• Help link and help label
• Choice of displaying institutional links
• Popular with clients
26. Prerequisite:
◦ Must have service-specific attributes that control
the customization.
◦ Extend service registry with custom UI elements; or
◦ Plug in custom service registry (see above.)
27. Step 1: Write a Spring Web Flow Action class to map the
incoming Service to a RegisteredService and make the
RegisteredService available in the web flow context.
Public class ServiceUiElementsResolverAction extends AbstractAction {
...
protected Event doExecute(RequestContext requestContext) throws Exception {
// get the Service from requestContext.
Service service = (Service) requestContext.getFlowScope().get("service",
Service.class);
...
// get the RegisteredService for this request from the ServicesManager.
WindRegisteredService registeredService = (WindRegisteredService)
this.servicesManager.findServiceBy(service);
...
// make RegisteredService available to the view.
requestContext.getRequestScope().put("registeredService",
registeredService);
...
}
...
}
28. Step 2: Define a bean for the Action class in cas-
servlet.xml, to make the class available to the login web
flow:
cas-servlet.xml
...
<bean id="uiElementsResolverAction“
class="edu.columbia.cas.wind.ServiceUiElementsResolverAction">
<constructor-arg index="0" ref=“servicesManager"/>
</bean>
29. Step 3: Make the RegisteredService available to the web flow by
doing our Action in the login web flow just before the login UI is
rendered:
Login-webflow.xml
...
<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
<!– Make RegisteredService available in web flow context -->
<evaluate expression="uiElementsResolverAction"/>
</on-entry>
<transition on="submit" bind="true" validate="true" to="realSubmit">
<evaluate expression="authenticationViaFormAction.doBind
(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>
30. Step 4: In the login view, refer to RegisteredService
attributes when customizing the UI markup:
casLoginView.jsp
...
<!-- Derive the path to the logo image from the registered service. -->
<c:set var="imagePath" value =
"${!empty registeredService.imagePath
? registeredService.imagePath : defaultImagePath}"/>
...
<!-- display the custom logo -->
<img src="<c:url value="${imagePath}" />" alt="${registeredService.name}"
/>
...
31. Result…
◦ Vanilla login page
◦ Login page with default logo, institutional links
◦ Login page with custom logo
◦ Login page with another custom logo and help link
32. CAS allows a login without a service, a generic
login, which creates a ticket granting ticket but no
service ticket.
Generic login permitted
Legacy auth service assumes client is always trying
to log into something. Treats a generic login as an
error. We want to preserve this behavior.
33. Step 1: Write a Spring Web Flow Action that checks if
the login request has a known service destination and
returns success/error.
public class CheckForRegisteredServiceAction extends AbstractAction {
ServicesManager servicesManager;
protected Event doExecute(RequestContext requestContext)
throws Exception
{
Service service = (Service)
requestContext.getFlowScope().get("service", Service.class);
RegisteredService registeredService = null;
if(service != null) {
registeredService = this.servicesManager.findServiceBy(service);
}
return ( registeredService==null ) ? error() : success();
}
}
34. Step 2: Make the class available to the login web
flow by defining a bean in cas-servlet.xml:
cas-servlet.xml
...
<bean id="checkForRegisteredServiceAction“
class="edu.columbia.cas.wind.CheckForRegisteredServiceAction"
>
<constructor-arg index="0" ref="servicesManager"/>
</bean>
...
35. Step 3: In the login web flow add an action-state to check
that the request has a service parameter, and it corresponds
to a RegisteredService.
login-webflow.xml
...
<!-- validate the request: non-null service with corresponding
RegisteredService -->
<decision-state id="hasServiceCheck">
<if test="flowScope.service != null" then="hasRegisteredServiceCheck“
else="viewServiceErrorView" />
</decision-state>
<!-- Is there a corresponding RegisteredService? -->
<action-state id="hasRegisteredServiceCheck">
<evaluate expression="checkForRegisteredServiceAction"/>
<transition on="success" to="ticketGrantingTicketExistsCheck" />
<transition on="error" to="viewServiceErrorView" />
</action-state>
36. Result…
◦ CAS will now assume client is always trying to log
into something and treat a request without a known
service destination as an error.
◦ Users will not see login UI less they arrive with a
registered service.
◦ Generic login not permitted
37. Tasks accomplished:
◦ Support Google Apps SSO
◦ Plug legacy service registry into CAS
◦ Add legacy authentication protocol to CAS
◦ Port login UI customizations to CAS
◦ Eliminate generic login
38.
39. Dan Ellentuck, Columbia University
de3@columbia.edu
Bill Thompson, Unicon Inc.
wgthom@unicon.net