Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

This page is for design and discussion of how to make JSR-168 Portlets use Proxy CAS.

Warning
titleContents out of date

This page should be updated to include discussion of uPortal 3.x UserInfoService-based CAS Proxy strategy

Why use Proxy CAS

...

What is Proxy CAS?

Proxy CAS allows authentication of a chain of services (say, your Email Calendar Portlet as accessed from your uPortal instance) and its participation in a Single Sign On session (say, end user "awp9" authenticated via CAS to your uPortal, which proxy authenticated via CAS to your JSR-168 email calendar portlet, which proxy authenticated via CAS to your IMAP calendar feed server). All of this happens with the end user authenticated to CAS using, say, a username and password and the services authenticated to CAS via server-side SSL certificates. No forwarding of primary credentials - only CAS server gets to see the password.

How proxy CAS works for uPortal channels

Use CAS for login to your portal. At the time the portal validates the service ticket presented to it, it requests a proxy granting ticket. CAS calls back to uPortal's SSL'ed servlet to deposit the proxy granting ticket, thereby authenticating the uPortal instance.

This Proxy Granting Ticket identifier is available from the IYaleCasContext security context. Channels use a CasConnectionContext (a subclass of the generic LocalConnectionContext API) to place proxy tickets onto URLs they'd like to use. (Specifically, CasConnectionContext calls the IYaleCasContext to use the proxy granting ticket to obtain a proxy ticket for the URL and append it to the URL).

Anyway, channels use CasConnectionContext to proxy to backing sources.

Suppose you write an IChannel that speaks CASified IMAP: then the proxy chain is:
uPortal --> IMAP server.

Architecture: how Proxy CAS can work for JSR-168 portlets

Option 1: Portlets are like channels

Just like was the case with CASified IChannels, the proxy chain is:
uPortal --> IMAP server.

Just like with IChannels, the uPortal at user authentication to the portal via CAS requests and obtains a Proxy Granting Ticket in the name of the portal itself.

Modify (or subclass) CPortletAdapter to pass the Proxy Granting Ticket identifier to the JSR-168 portlet.

The JSR-168 can then use the PGT to acquire proxy tickets in the name of the uPortal, proxying the end user's authentication. Very similar to the way IChannels do this, except we're not using the LocalConnectionContext and instead we're doing something more CAS-specific.

Option 2: Doing better than channels

There's a tremendous opportunity here to do "better" with Proxy CAS in JSR-168 portlets than we did with proxy CAS in IChannels. This opportunity is borne of the modularity of JSR-168 portlets and their availability as web applications.

...

uPortal Portlet ProxyCAS Strategy

The uPortal obtains a Proxy Granting Ticket in the name of the uPortal instance at the time the user authenticates. We modify (or subclass) CPortletAdapter to use the Proxy Granting Ticket to obtain a Proxy Ticket addressed to a dummy service URL agreed to by portal and portlet. Say, something like httpThe Cas Proxy Ticket User Info Service then requests a proxy ticket for a URL representing the portlet (for example, "https://my.someschoolschool.edu/portletCasServiceTargetsCalendarPortlet/portletApp.portlet , where portletApp.portlet identifies the particular portlet. Nothing's actually listening at this URL, it's just a convention.

CPortletAdapter passes the proxy ticket to the JSR-168. The JSR-168 validates the proxy ticket and when it does so it requests a Proxy Granting Ticket be issued. CAS does an SSL callback to authenticate the portlet. Since the portlet is a real web application, it can map a listener to receive the proxy granting ticket.

Then the portlet uses that proxy granting ticket to acquire proxy tickets to proxy to back end services.

What we've gained here is we've included the particular authenticated portlet in the proxy chain, such that the ultimate target of these proxy tickets can verify not just what user's session we're part of, not just that it's your uPortal asking, but which portlet it is that's asking. An HR portlet is differentiable from the email portlet, such that the email portlet couldn't proxy into your payroll system and the HR portlet couldn't proxy in and get my email. More specific, better granularity of authentication. Something we didn't have with IChannels.

Implementation

Suppose we want to use option 2.

Subclassing CPorletAdapter

We can subclass CPortletAdapter to produce a version of CPortletAdapter that acquires and supplies a proxy ticket without imposing this functionality on every portlet. JSR-168s taking advantage of our CAS-specific usages can work in uPortal via the more specific CPortletAdapter subclass.

So let's subclass CPortletAdapter to produce CasPortletAdapter.

Subclassing lets us override two things. One is the user attributes presented by portal to portlet. Let's leave those alone and accept the default CPortletAdapter behavior for those. Another is request attribute generation. Let's override that to add a request attribute in which to pass the String that is the proxy ticket and another attribute to pass the String that is the "service" identifier required for proxy ticket validation. Our CasPortletAdapter will need to take an additional channel static data parameter configuring that "service" identifier.

Okay. So CasPortletAdapter reads a channel static data parameter to know the "service" identifier, accesses the IYaleCasContext security context to acquire a proxy ticket for that service identifier, and includes both the service identifier and the proxy ticket as Portlet request attributes.

Making our JSR-168 CAS-aware

The JSR-168 needs to read the portlet request parameters conveying the proxy ticket and the service identifier. It then needs to use ProxyTicketValidator from the CAS Java Client jar to validate the proxy ticket, specifying a proxy callback URL that is mapped in web.xml to an instance of the CAS Java Client ProxyTicketReceptor servlet. The proxy callback URL should probably be configured in portlet.xml as a parameter to our portlet.

The portlet invokes the ProxyTicketValidator to validate the ticket and then uses it to access the PGTIOU. It can then use the PGTIOU to access the ProxyTicketReceptor and thereby acquire proxy tickets to access the resources to which it would like to proxy.

It should store the pgtiou, or perhaps a CASReceipt, into the Portlet session so that it won't need to receive and validate a proxy ticket on every request from portal to portlet.

Is JSR-168 compliant?

This introduces CAS-specific extensions within the framework of JSR-168: specifically, CAS-specific Portlet request attributes. A JSR-168 written to take advantage of CAS proxy functionality using this approach will require its portlet container to provide these CAS-specific request attributes. This means that the portlet won't work in a random portlet container that doesn't know how to provide these CAS-specific portlet attributes.

Code sketches

Related thoughts

If you squint hard enough, a Sakai Tool in Sakai looks a whole lot like a JSR-168 portlet in uPortal. These Powerpoint slides attached to this Wiki page include notes about how such tools might take full advantage of proxy CAS.

The ESUP-Portail implementation

Pass a Proxy Ticket to portlets 

The ESUP-Portail project uses a subclass of CPortletAdaptor called CCasProxyPortletAdaptor (CCasProxyPortletAdapter.java). This class passes a Proxy Ticket through JSR-168 preferences to the portlets.

In order to be CAS-aware, the portlet must be published with this new adaptor, for instance:

...

CasProxyServlet").

The user info service places the resulting proxy ticket in the portlet's UserInfo map under the configured attribute name (by default "casProxyTicket"). The portlet must then validate the proxy ticket and request that a proxy granting ticket be issued to the portlet. This proxy granting ticket may subsequently be used to acquire proxy tickets for other services (e.g. some calendar feed server).

Using ProxyCAS with a Portlet

Configure the CAS Ticket User Info Service

To use the CAS Ticket User Info Service, ensure the bean is mapped in uportal-impl/src/main/resources/properties/contexts/portletContainerContext.xml and included in the userInfoService bean list. This bean is present and appropriately configured in the default uPortal distribution, but it is provided below for completeness.

Code Block
xml
xml

<bean id="userInfoService" class="org.jasig.portal.portlet.container.services.MergingUserInfoService">
        <property name="userInfoServices">
            <list>
           <parameter>     <ref bean="personDirectoryUserInfoService"/>
      <name>portletDefinitionId</name>             <value>esup-blank.esup-blank</value><ref bean="casTicketUserInfoService"/>
            <ovrd>N<</ovrd>list>
        </parameter>property>
    </bean>

  <parameter>  <bean id="casTicketUserInfoService" class="org.jasig.portal.portlet.container.services.CasTicketUserInfoService">
        <name>casProxyTicketPref</name>
  <property name="userInstanceManager" ref="userInstanceManager" />
         <value>casProxyTicket</value>
 <property name="portletWindowRegistry" ref="portletWindowRegistry" />
        <property  <description>
name="portletEntityRegistry" ref="portletEntityRegistry" />
        <property     The name of the JSR-168 preference used toname="portletDefinitionRegistry" ref="portletDefinitionRegistry" />
        <property name="portalRequestUtils" ref="portalRequestUtils" />
     pass the proxy ticket. Optional, defaults to 'casProxyTicket'
            </description>
            <ovrd>N</ovrd>
        </parameter>
        <parameter>
            <name>casTargetService</name>
            <value>http://portal.domain.edu/application</value>
            <description>The CAS service of the portlet</description></bean>

Add the CAS Proxy Ticket Attribute to portlet.xml

The proxy ticket user info service will not request a proxy ticket for the portlet unless the portlet specifically requests it. To ensure a proxy ticket is placed in the UserInfo map, add a user-attribute mapping to the portlet's portlet.xml file. The configured attribute name must precisely match the key name configured in uPortal's CasTicketUserInfoService. This directive is placed outside the <portlet/> element:

Code Block
xml
xml

<portlet-app
    xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
    version="1.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd
            <ovrd>N</ovrd>         </parameter>     </parameters>
</channel-definition>

Once published this way, the portlet is automatically an additional JSR-168 preference named casProxyTicket that can be validated by the CAS server to get a PGT. The PGT can later retrieve Proxy Tickets for backend services. Parameter casTargetService is a dummy portal URL, the portlet should use the same URL when validating the ticket.

Note: no PT is passed to the portlet if parameter casTargetService is not set, so the adaptor can be used for any portlet.

Get a Proxy Ticket from the portlet for a backend service

Proxy Tickets for backend services are retrieved by calling a bean (casService):

Code Block

String casTargetService = "http://service.domain.edu/path";
String pt = null;
try {http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" id="ProxyCasPortlet">

    <portlet>

   pt = casService.getProxyTicket(casTargetService); } catch (CasException. e) {
    ...
}

The bean casService is defined like this:

Code Block

<bean. .

    </portlet>

id="casService"     class="[...].commons.services.cas.PortletCasServiceImpl" ><user-attribute>
      <property  <!--
    name="service"       value="http://portal.domain.edu/application" />
    <property
      name="casValidateUrl"
      value="https://cas.domain.edu/proxyValidate" /> This attribute name must match the key name configured in uPortal's
         <property   CasTicketUserInfoService
  name="proxyCallbackUrl"      value="https://portal.domain.edu/application/casProxyCallback" /-->
    <property    <name>casProxyTicket</name>
 name="casProxyTicketPref"   </user-attribute>

 value="casProxyTicket" />
</bean>portlet-app>

...

Get a Proxy Ticket

...

Of course, the tomcat context of the portlet must define the callback URL:

...

from the portlet for a backend service

Using Proxy CAS using the 3.1.x Jasig CAS client generally involves configuring filters, then using the CAS Java API to validate the UserInfo map provided CAS ticket.This works as follows:

First the portlet retrieves its own proxy ticket from the portlet's UserInfo map. Next it can use the org.jasig.cas.client.validation.TicketValidator class to validate the ticket and retrieve an Assertion object. The Assertion object encapsulates the Ticket Granting Ticket that the portlet can now use to obtain service tickets for back end services and resources. The Assertion object, once obtained is durable for the life of the portlet instance and can be used multiple times to obtain service tickets. The Assertion can be stored in the session and re-used by other components to obtain service tickets as needed.

Current CAS best practices suggest using Spring's DelegatingFilterProxy to configure a CAS filters via the Spring application context. This strategy allows developers to make use of Java property files for configuring strings such as the portal's base server URL.

Sample code and configuration for getting proxy tickets from a portlet is available in the Cas Proxy Test Portlet. Further documentation on using CAS's filters is available at https://wiki.jasig.org/display/CASC/Configuring+the+JA-SIG+CAS+Client+for+Java+using+Spring.