Overview
WebproxyPortlet v2 Gateway SSO is a feature that allows uPortal to signon to any remote system even if the remote system does not share any authentication information with uPortal. Gateway SSO will submit login information to the remote system and then redirect to that remote system. Other SSO solution assume that uPortal has authenticated to some system, such as CAS and will then trust CAS to say the user is authenticated. In this system, the authentication information is submitted to the remote system invisibly to the user. This solution has the inherent risks of sending user authentication information over the wire, rather than a security token, but this solution does not require external systems to implement CAS or another authentication system. It is therefore nearly invisible to any external system to which uPortal would want to connect.
Workflow
The workflow for accessing an external system through Gateway SSO is as follows (assuming that the portlet is configured and will be rendered on the user's page
- The main portlet controller loops through each GatewayEntry associated with the portlet.
- Each GatewayEntry runs through each Interceptor associated with it to ensure that the entry is valid and then calls the main JSP to render.
- Each GatewayEntry is rendered on the page, displaying the name, icon and if valid a link to the external system. Invalid external systems display a message about the issue.
- The user then clicks on the link for the external system they wish to access. This makes an Ajax call to the handling controller.
- The handling controller gathers all of the form information stored in the HttpContentRequestImpl parameters configuration and readies it for return to the calling JSP
- All configured Interceptors perform any substitutions on configuration data
- The external logic adds any additional required information or performs any other setup required.
- The controller returns all of the gathered data to the calling JSP.
- The JSP Ajax handler builds an appropriate form and submits it to the external system. The external system then handles the call and will render whatever page a successful login would render.
Portlet Configuration
Gateway SSO must be defined in portlet.xml, like any other portlet.
<portlet> <portlet-name>GatewayPortlet</portlet-name> <portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class> <init-param> <name>contextConfigLocation</name> <value>/WEB-INF/context/portlet/gateway-sso-portlet.xml</value> </init-param> <expiration-cache>0</expiration-cache> <supports> <mime-type>text/html</mime-type> <portlet-mode>view</portlet-mode> <portlet-mode>edit</portlet-mode> </supports> <portlet-info> <title>WebProxy Portlet</title> </portlet-info> <portlet-preferences> <preference> <name>openInNewPage</name> <value>true</value> </preference> </portlet-preferences> </portlet>
One interesting portlet preference is "openInNewPage". True will direct the response after clicking on the link to a new tab in your browser; false will direct to the current tab.
The description of the portlet itself lives in the portlet definition file:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <context:component-scan base-package="org.jasig.portlet.proxy.mvc.portlet.gateway" /> <context:annotation-config /> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:configuration.properties" /> </bean> <util:list id="gatewayEntries" scope="session"> <bean class="org.jasig.portlet.proxy.mvc.portlet.gateway.GatewayEntry" p:name="MyZimbra" p:iconUrl="/ResourceServingWebapp/rs/tango/0.8.90/32x32/apps/internet-mail.png"> <property name="externalLogic"> <util:list> <bean id="step1" class="org.jasig.portlet.proxy.service.web.MyCustomClass" scope="session" p:fieldName="proxiedLocation"/> <bean id="step2" class="org.jasig.portlet.proxy.service.web.MyCustomClass2" scope="singleton" p:fieldName="testField" /> </util:list> </property> <property name="contentRequests"> <util:map> <entry> <key> <bean class="org.jasig.portlet.proxy.service.web.HttpContentRequestImpl" p:proxiedLocation="https://zimbra.unicon.net/zimbra/" p:form="true" p:method="POST"> <property name="parameters"> <util:map> <entry> <key><value>loginOp</value></key> <bean class="org.jasig.portlet.proxy.service.web.FormFieldImpl" p:name="loginOp" p:value="login"/> </entry> <entry> <key><value>username</value></key> <bean class="org.jasig.portlet.proxy.service.web.FormFieldImpl" p:name="username" p:value="{prefs.myzimbra.uid}"/> </entry> <entry> <key><value>password</value></key> <bean class="org.jasig.portlet.proxy.service.web.FormFieldImpl" p:name="password" p:value="{prefs.myzimbra.pwd}" p:secured="true"/> </entry> </util:map> </property> </bean> </key> <util:list> <value>userInfoUrlParameterizingPreInterceptor</value> <value>UserPreferencesPreInterceptor</value> </util:list> </entry> </util:map> </property> </bean> <!-- additional external systems to display --> <bean ...></bean> </util:list> <bean class="org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping"> <property name="interceptors"><bean class="org.jasig.portlet.proxy.mvc.MinimizedStateHandlerInterceptor"/></property> </bean> </beans>
GatewayEntry
Each external systems to which you want to connect has its own GatewayEntry record. This record contains the name and location of the Icon to represent the application. It also contains a list of HttpContentRequestImpl records and any external logic to be executed.
HttpContentRequestImpl
In theory each GatewayEntry could have multiple HttpContentRequestImpl records associated with it; in practice, each GatewayEntry would only have on HttpContentRequestImpl record. This record contains the web address that will receive the form submission, the HTTP method type (GET, POST, PUT, DELETE), and a list of form fields to be included in the form submission.
FormFieldImpl
FormFieldImpl contains the information about each field that will be included in the form submitted to the external system. The relevant fields are:
- name - the name of the HTML field
- value - the value of the HTML field. This field either a static value or a value that an Interceptor will recognize as being overridden.
- secured - whether the field should be sent as input type of "text" or "password". If secured is true, it sends as a password field. In addition, if the secured field is an overridden field that is editable, the editable field will be obscured when editable and will be encrypted when stored.
Interceptors
Interceptors examing FormFieldImpl objects before they are returned to the JSP and perform a substitution if the field should be substituted. The interceptor itself is responsible for knowing whether the value should be overridden. For example,
<bean class="org.jasig.portlet.proxy.service.web.HttpContentRequestImpl" p:proxiedLocation="https://zimbra.unicon.net/zimbra/" p:form="true" p:method="POST"> <property name="parameters"> <util:map> <entry> <key><value>loginOp</value></key> <bean class="org.jasig.portlet.proxy.service.web.FormFieldImpl" p:name="loginOp" p:value="login"/> </entry> <entry> <key><value>username</value></key> <bean class="org.jasig.portlet.proxy.service.web.FormFieldImpl" p:name="username" p:value="{prefs.myzimbra.uid}"/> </entry> ... </util:map> </property> </bean>
The FormFieldImpl entry for loginOp shows a value of "login". No current interceptors would match this.
The FormFieldImpl entry for username shows a valud of "{prefs.myzimbra.uid}", which the UserPreferencePreInterceptor would recognize as a PortletPreference and would substitute the value found for this field.
IPreInterceptor
IPreInterceptor is an interface that all Interceptors must implement. Interceptors substitute values into FormFieldImpl objects. Multiple interceptors can be associated with HttpContextRequestImpl objects.
UserPreferencePreInterceptor
UserPreferencePreInterceptor overrides FormFieldImpl values with values that are stored in PortletPreferences. When this interceptor runs, it looks for FormFieldImpl values that match the regex. By default, your portlet will use the regex of "\\{prefs\\.[\\w.]+\\}", but this can be changed in configuration.properties of the portlet. An example of a valid FormFieldImpl value would be "{prefs.myzimbra.uid}".
UserInfoUrlParameterizingPreInterceptor
UserPreferencePreInterceptor overrides FormFieldImpl values with the values stored in UserInfo. uPortal can be configured to store your uPortal login and password, making them available to userInfo. If uPortal is configured this way, this interceptor will send the same uid and password that you used to authenticate to uPortal to the external system. Since uPortal and the external system do not share an authentication system, it is still possible for the two systems to get out of sync. An example of a valid FormFieldImpl value would be "{user.login.id}"
Custom Logic
Custom Logic can be embedded in your Gateway SSO to support more complicated scenarios. The user can create their own Java Bean that implements the ExternalLogic interface and include it in the portlet definition. When the controller gathers all of the information required by the JSP to render, in addition to any fields defined in the associated FormFieldImpl fields, it will add a new field for each CustomLogic bean.
For example, say you have an external system to which you wish to login. However, before you actually login, you need to retrieve a token from another system and include it in the submitted form or on the form's action field. You can write a custom logic bean that will retrieve that value and include it as a field in the submission. If you wish to create a custom form action, you could return a field named "proxiedLocation" , which would override the proxied location stored with the HttpContentRequestImpl field.
ExternalLogic
ExternalLogic is an interface that must be implemented by any classes that wish to hook into the Custom Logic process. The methods are:
- getFieldName() - returns the field name to be included in the list of parameters
- getResult(PortletPreferences preferences) - returns the value of the field.
Because this is a bean, your implementation can be as simple or as complicated as needed. PortletPreferences will be available to your bean.