We are currently using Acegi/CAS to achieve single sign on across
multiple Tomcat servers. Everything is working great, but I did have
some issues with the ServiceProperties.service property being hard
coded in the Spring config file. In our deployment configuration, the
apps are load balanced across multiple servers. In a normal scenario,
the app is accessed through the load balancer which will talk to a
'Service'(http://jakarta.apache.org/tomcat/tomcat-5.5-doc/config/
service.html) via the AJP connector on one of the several backend
servers. It is often useful to access the service directly on a backend
machine via the HTTP connector. For example to check if a specific
back-end is sane.
Ex. Lets say I have myApp deployed across two Tomcat servers. On each
server, the AJP connector is configured on port 8009 and the HTTP
connector is configured on 8080. Normally the the app would be accessed
via https://prism.aps.org/myApp which would land the client on either
server1:8009 or server2:8009.
https://prism.aps.org/myApp -> http://server1:8009/myApp
->
http://server2:8009/myApp
In the web.xml of of myApp, I would have:
<bean id="serviceProperties"
class="net.sf.acegisecurity.ui.cas.ServiceProperties">
<property
name="service"><value>https://prism.aps.org/myApp/
j_acegi_cas_security_check</value></property>
</bean>
With this configuration, if I go directly to http://server1:8080/myApp,
I'll be redirected back to
https://prism.aps.org/myApp/j_acegi_cas_security_check, which will go
through the load balancer and land me one either server1 or server2.
I needed to be able to access myApp directly via
http://server1:8080/myApp or https://prism.aps.org with the same
configuration. I was able to accomplish this by creating a subclass of
ServiceProperties that stores the service url in a thread local
variable. I configure a filter that runs before any of the CAS filters
and dynamically builds and stores the service URL based on the current
HttpRequest.
<!-- ======================== FILTER CHAIN ======================= -->
<bean id="filterChainProxy"
class="net.sf.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/
**=resetServicePropertiesFilter,httpSessionContextIntegrationFilter,auth
enticationProcessingFilter,securityEnforcementFilter </value>
</property>
</bean>
....
.....
<bean id="resetServicePropertiesFilter"
class="org.aps.webprowo.prism.acegi.ResetServicePropertiesFilter">
<property name="serviceProperties"><ref
bean="serviceProperties"/></property>
</bean>
.......
.......
<bean id="serviceProperties"
class="org.aps.webprowo.prism.acegi.ThreadLocalServiceProperties"/>
<!-- end -->
I'm attaching the following files:
ThreadLocalServiceProperties - maintains a ThreadLocal variable holding
the current service URL.
ResetServicePropertiesFilter -
* This Filter derives the proper service url from the current request
* and sets it up in ThreadLocalServiceProperties to be used by CAS
* throughout the request.
* <p>
* This is to enable us to access a service through multiple URL's.
* (http://backend:8080 and https://prism.aps.org)
*
* <p>
* This Filter should be configured to run before any of the CAS
filters.
This has been working for us for a couple of months now. Please let me
know if you think it's useful outside of APS or if there is an easier
way that I had missed.
Thanks,
Lenny Marks
Software Architect
American Physical Society
/*
* Created on Jun 9, 2005
*
*/
package org.aps.webprowo.prism.acegi;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
/**
* @author Lenny Marks ([EMAIL PROTECTED])
*
* This Filter derives the proper service url from the current request
* and sets it up in ThreadLocalServiceProperties to be used by CAS
* throughout the request.
* <p>
* This is to enable us to access a service through multiple URL's.
* (http://backend:8080 and https://prism.aps.org)
*
* <p>
* This Filter should be configured to run before any of the CAS filters.
*
*/
public class ResetServicePropertiesFilter implements Filter {
private static final Logger LOGGER =
Logger.getLogger(ResetServicePropertiesFilter.class.getName());
private ThreadLocalServiceProperties serviceProperties;
private String servicePath = "j_acegi_cas_security_check";
public ResetServicePropertiesFilter() {
super();
}
public ThreadLocalServiceProperties getServiceProperties() {
return serviceProperties;
}
public void setServiceProperties(
ThreadLocalServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
public String getServicePath() {
return servicePath;
}
public void setServicePath(String servicePath) {
this.servicePath = servicePath;
}
/* (non-Javadoc)
* @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
public void init(FilterConfig arg0) throws ServletException {
}
/* (non-Javadoc)
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
serviceProperties.setService(serviceURLForRequest(request));
chain.doFilter(request, response);
}
/* (non-Javadoc)
* @see javax.servlet.Filter#destroy()
*/
public void destroy() {
}
private String serviceURLForRequest(ServletRequest request) {
HttpServletRequest httpRequest = (HttpServletRequest)request;
StringBuffer buf = httpRequest.getRequestURL();
int contextPathIndex = buf.indexOf(httpRequest.getContextPath());
buf.setLength(contextPathIndex);
buf.append(httpRequest.getContextPath() + "/" + servicePath);
LOGGER.debug("serviceUrl is " + buf);
String serviceURL = buf.toString();
serviceProperties.setService(serviceURL);
return serviceURL;
}
}
/*
* Created on Jun 9, 2005
*
*/
package org.aps.webprowo.prism.acegi;
import net.sf.acegisecurity.ui.cas.ServiceProperties;
/**
* @author Lenny Marks ([EMAIL PROTECTED])
*
* See PRISMCasProcessingFilterEntryPoint
*/
public class ThreadLocalServiceProperties extends ServiceProperties {
private ThreadLocal service = new ThreadLocal();
public ThreadLocalServiceProperties() {
super();
}
public void afterPropertiesSet() throws Exception {
}
public String getService() {
return (String)service.get();
}
public void setService(String s) {
service.set(s);
}
}