jasigsakai12 columbia-customizes-cas

39
June 10-15, 2012 Growing Community; Growing Possibilities Dan Ellentuck, Columbia University Bill Thompson, Unicon Inc.

Upload: ellentuck

Post on 29-Nov-2014

1.183 views

Category:

Technology


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Jasigsakai12 columbia-customizes-cas

June 10-15, 2012

Growing Community; Growing Possibilities

Dan Ellentuck, Columbia University

Bill Thompson, Unicon Inc.

Page 2: Jasigsakai12 columbia-customizes-cas

Reasons to Choose CAS:Google Apps SSOSAML SupportVendor SupportCommunity SupportTie-in with other open source tools and products, e.g.,

Sakai

Complicating Factors:Pre-existing local web auth systemActive, diverse client base

Question:How can legacy system be migrated to CAS?

Page 3: Jasigsakai12 columbia-customizes-cas

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

Page 4: Jasigsakai12 columbia-customizes-cas

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=6063484

Works OOTB.

Page 5: Jasigsakai12 columbia-customizes-cas

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:

WIND:

/login?destination=https://MY-APPLICATION-PATH

/logout

/validate?ticketid=SERVICE-TICKET

CAS:

/login?service=https://MY-APPLICATION-PATH

/logout

/serviceValidate?service=https://APPLICATION-PATH&ticket=SERVICE-TICKET

Page 6: Jasigsakai12 columbia-customizes-cas

2 private validation response formats (text and xml):

<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>

yes

de3

Page 7: Jasigsakai12 columbia-customizes-cas

Service registry with maintenance UI Service attributes for UI customization, multiple destinations,

attribute release, application contacts, etc.

SINGLE_SIGN_ON (T/F)PROXY_GRANTING (T/F)RETURN_XML (T/F)ID_FORMATDESCRIPTIONHELP_URI (for customizing UI)IMAGE_PATH(for customizing UI )HELP_LABEL(for customizing UI)

SERVICE DESTINATION

DESTINATION

SERVICE_CONTACT

EMAIL_ADDRESS

CONTACT_TYPE

AFFILIATION (like ATTRIBUTE)

AFFILIATION

SERVICE_LABEL

SERVICE_LABEL

SERVICE_LABEL

SERVICE_LABEL

Page 8: Jasigsakai12 columbia-customizes-cas

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.

Page 9: Jasigsakai12 columbia-customizes-cas

• Java

• View technologies (JSP, CSS, etc.)

• Maven (dependencies; overlays)

• Spring configuration (CAS set up)

• Spring Web Flow (SWF)

• App server/web server (tomcat/apache)

Page 10: Jasigsakai12 columbia-customizes-cas

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

Page 11: Jasigsakai12 columbia-customizes-cas

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.

}

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.

Page 12: Jasigsakai12 columbia-customizes-cas

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;

}

...

}

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.

Page 13: Jasigsakai12 columbia-customizes-cas

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);

}

...

}

Step 3: Implement a CAS ServicesManager (maps incoming Service URL of a request with the matching CAS RegisteredService.)

Page 14: Jasigsakai12 columbia-customizes-cas

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.

Step 4: Write Spring bean definitions for the new ServicesManager.

Page 15: Jasigsakai12 columbia-customizes-cas

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

Page 16: Jasigsakai12 columbia-customizes-cas

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

Page 17: Jasigsakai12 columbia-customizes-cas

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);

}

Step 1: Implement the CAS Service interface for the new protocol by subclassing abstractWebApplicationService:

Page 18: Jasigsakai12 columbia-customizes-cas

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());

Page 19: Jasigsakai12 columbia-customizes-cas

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>

...

Page 20: Jasigsakai12 columbia-customizes-cas

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()

}

Page 21: Jasigsakai12 columbia-customizes-cas

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>

Page 22: Jasigsakai12 columbia-customizes-cas

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.

Page 23: Jasigsakai12 columbia-customizes-cas

2012 Jasig Sakai Conference 23

Page 24: Jasigsakai12 columbia-customizes-cas

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.

Page 25: Jasigsakai12 columbia-customizes-cas

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

Page 26: Jasigsakai12 columbia-customizes-cas

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.)

Page 27: Jasigsakai12 columbia-customizes-cas

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);

...

}

...

}

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.

Page 28: Jasigsakai12 columbia-customizes-cas

cas-servlet.xml

...

<bean id="uiElementsResolverAction“

class="edu.columbia.cas.wind.ServiceUiElementsResolverAction">

<constructor-arg index="0" ref=“servicesManager"/>

</bean>

Step 2: Define a bean for the Action class in cas-servlet.xml, to make the class available to the login web flow:

Page 29: Jasigsakai12 columbia-customizes-cas

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>

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:

Page 30: Jasigsakai12 columbia-customizes-cas

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}"

/>

...

Step 4: In the login view, refer to RegisteredServiceattributes when customizing the UI markup:

Page 32: Jasigsakai12 columbia-customizes-cas

CAS allows a login without a service, a genericlogin, 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.

Page 33: Jasigsakai12 columbia-customizes-cas

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();

}

}

Step 1: Write a Spring Web Flow Action that checks if the login request has a known service destination and returns success/error.

Page 34: Jasigsakai12 columbia-customizes-cas

cas-servlet.xml

...

<bean id="checkForRegisteredServiceAction“

class="edu.columbia.cas.wind.CheckForRegisteredServiceAction">

<constructor-arg index="0" ref="servicesManager"/>

</bean>

...

Step 2: Make the class available to the login web flow by defining a bean in cas-servlet.xml:

Page 35: Jasigsakai12 columbia-customizes-cas

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>

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.

Page 36: Jasigsakai12 columbia-customizes-cas

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

Page 37: Jasigsakai12 columbia-customizes-cas

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

Page 38: Jasigsakai12 columbia-customizes-cas