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

}

Reply via email to