Jasigsakai12 columbia-customizes-cas


Published on

Published in: Technology
  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

Jasigsakai12 columbia-customizes-cas

  1. 1. Dan Ellentuck, Columbia University Bill Thompson, Unicon Inc. June 10-15, 2012Growing Community;Growing Possibilities
  2. 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. 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. 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. 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-TICKETWIND:/login?destination=https://MY-APPLICATION-PATH/logout/validate?ticketid=SERVICE-TICKET
  6. 6.  2 private validation response formats (text and xml): yes de3 <wind:serviceResponse xmlns:wind=http://www.columbia.edu/acis/rad/authmethods/wind> <wind:authenticationSuccess> <wind:user>de3</wind:user> <wind:passwordtyped>true</wind:passwordtyped> <wind:logintime>1338696023</wind:logintime> <wind:passwordtime>1331231507</wind:passwordtime> <wind:passwordchangeURI>https://idmapp.cc.columbia.edu/acctmanage/changepasswd </wind:passwordchangeURI> </wind:authenticationSuccess> </wind:serviceResponse>
  7. 7.  Service registry with maintenance UI  Service attributes for UI customization, multiple destinations, attribute release, application contacts, etc.SERVICE DESTINATION SERVICE_LABELSERVICE_LABEL DESTINATIONSINGLE_SIGN_ON (T/F)PROXY_GRANTING (T/F)RETURN_XML (T/F) SERVICE_CONTACTID_FORMATDESCRIPTION SERVICE_LABELHELP_URI (for customizing UI) EMAIL_ADDRESSIMAGE_PATH(for customizing UI ) CONTACT_TYPEHELP_LABEL(for customizing UI) AFFILIATION SERVICE_LABEL AFFILIATION (like ATTRIBUTE)
  8. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 21.  Step 5: Define and wire up beans for the various protocol operations:argumentExtractorsConfiguration.xmldefines 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.xmlprotocol 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. 22.  Step 5: Define and wire up beans for the various protocol operations (cont’d):cas-servlet.xmlbean 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.
  23. 23. 2012 Jasig Sakai Conference 23
  24. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 38. Dan Ellentuck, Columbia Universityde3@columbia.eduBill Thompson, Unicon Inc.wgthom@unicon.net