Yuriy Zubarev

CAS "branded" Authentication Filter

2010-02-15

Seemingly complex tasks can have surprisingly easy resolutions. All is needed is not trying to make things more complex than they are.

We have two domain names for two distinct categories of customers: www.example1.com and www.example2.com. These domains point to the very same Web application – Super Widgets 2.0. The application has to redirect new visitors to CAS log-in page. Super Widgets 2.0 provides “service” (or “serverName”, please read on) parameter to CAS so that CAS can redirect users back to Super Widgets 2.0 after a successful authentication. “service” parameter comes from web.xml of Super Widgets 2.0 and it’s a constant value as far as CAS Client is concerned. So the “service” value can either be http://www.example1.com or http://www.example2.com. When it comes to CAS, it knows where to redirect users after the authentication but it doesn’t know the original URL users tried to access (CAS Client doesn’t pass Referer HTTP header).

On CAS log-in page we would have to know the original URL so that we could greet users with either “Welcome to Example 1” or “Welcome to Example 2” messages (can be easily achieved with a simple JSP scriplet).

In the nutshell the solution (suggested by my colleague Alexey Malyshev) is to initialize and call org.jasig.cas.client.authentication.AuthenticationFilter from within our own custom filter. As we initialize Jasig’s AuthenticationFilter we calculate “service” value dynamically based on the value of request.getServerName(). I’m not really sure what design pattern should be selected form the list to describe this approach.

First, we need a custom but simple implementation of FilterConfig:

[sourcecode lang=“java”]
package com.superwidgets;


import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;

public class BrandingFilterConfig implements FilterConfig {

private FilterConfig parent; private Map<String, String> params = new HashMap<String, String>(); public BrandingFilterConfig(FilterConfig parent) { this.parent = parent; } public String getFilterName() { return “Super Widgets Branded Filter”; } public ServletContext getServletContext() { return this.parent.getServletContext(); } public String getInitParameter(String s) { if (this.params.containsKey(s)) { return this.params.get(s); } else { return this.parent.getInitParameter(s); } } public Enumeration getInitParameterNames() { return this.parent.getInitParameterNames(); } public void setParam(String key, String value) { params.put(key, value); } public void removeParam(String key) { params.remove(key); }

}
[/sourcecode]

Second, we need to implement our own Authentication Filter:

[sourcecode lang=“java”]
package com.superwidgets;

import javax.servlet.Filter;

public class AuthenticationFilter implements Filter {

protected final Log log = LogFactory.getLog(getClass()); private String serviceMapping; private Properties serviceProperties = new Properties(); private String serverNameMapping; private Properties serverNameProperties = new Properties(); private BrandingFilterConfig filterConfig; public String getServiceMapping() { return serviceMapping; } public void setServiceMapping(String serviceMapping) { this.serviceMapping = serviceMapping; } public void init(FilterConfig fc) throws ServletException { this.filterConfig = new BrandingFilterConfig(fc); // service this.serviceMapping = fc.getInitParameter(“serviceMapping”); log.info("serviceMapping = " + this.serviceMapping); try { if (this.serviceMapping != null) { this.serviceProperties.load(new ByteArrayInputStream(this.serviceMapping.getBytes())); } } catch (Exception ex) { log.error("Cannot load serviceMapping property: " + ex.getMessage()); } log.info("serviceProperties = " + this.serviceProperties); // serverName this.serverNameMapping = fc.getInitParameter(“serverNameMapping”); log.info("serverNameMapping = " + this.serverNameMapping); try { if (this.serverNameMapping != null) { this.serverNameProperties.load(new ByteArrayInputStream(this.serverNameMapping.getBytes())); } } catch (Exception ex) { log.error("Cannot load serverNameMapping property: " + ex.getMessage()); } log.info("serverNameProperties = " + this.serverNameProperties); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { org.jasig.cas.client.authentication.AuthenticationFilter realFilter = new org.jasig.cas.client.authentication.AuthenticationFilter(); String hostKey = request.getServerName(); log.debug("hostKey = " + hostKey); if (serviceMapping != null) { if (serviceProperties.containsKey(hostKey)) { this.filterConfig.setParam(“service”, serviceProperties.getProperty(hostKey)); } else { this.filterConfig.removeParam(“service”); } } if (serverNameMapping != null) { if (serverNameProperties.containsKey(hostKey)) { this.filterConfig.setParam(“serverName”, serverNameProperties.getProperty(hostKey)); } else { this.filterConfig.removeParam(“serverName”); } } realFilter.init(this.filterConfig); realFilter.doFilter(request, response, chain); realFilter.destroy(); } public void destroy() { // nothing to do } public Properties getServiceProperties() { return serviceProperties; } public Properties getServerNameProperties() { return serverNameProperties; }

}
[/sourcecode]

Now we have to modify web.xml and everything falls into places:

[sourcecode lang=“xml”]
-

CAS Authentication Filter
com.superwidgets.AuthenticationFilter

casServerLoginUrl
https://cas.superwidgets.com/login

service http://www.example1.com serviceMapping www.example2.com=http://www.example2.com www.example3.com=http://www.example3.com www.example4.com=http://www.example4.com

[/sourcecode]

serviceMapping” is a secret sauce here. The value is loaded in an instance of java.util.Properties class and then it’s used to dynamically calculate “service” value for Jasig’s AuthenticationFilter.

If AuthenticationFilter is using “serverName” parameter instead of “service”, then instead of “serviceMapping” parameter, “serverNameMapping” should be used.

A little summary. AuthenticationFilter has a dynamic behavior based on the original URL a user was trying to access. To achieve this we wrapped Jasig’s filter inside our own filter and we didn’t have to change Jasig’s filter internals in any way. Jasig’s AuthenticationFilter filter is initialized on every request and this has a light and acceptable (for us) performance implication.

 

Comments :

blog comments powered by Disqus