Monday, May 18, 2009

Acegi - CAS - Service Ticket - OC4J 10.1.3.*?! - Ticket lost

Thanks to Oracle OC4J (Standalone/Embedded) that from time to time, reminds me that we still can break the rules in software collaboration. The problem begins where you have a CAS - Acegi integrated SSO solution on some application server on a machine along with another application server with some applications on it using Oracle OC4J Standalone 10.1.3.*?! to host the applications. Now, when a client application goes to the CAS server and the SSO does the sing-in process, Acegi now should return to the client application using targetting the CasProcessingFilter:
https://oc4japphost:8443/myapp/j_acegi_security_check?ticket=[CAS SERVICE TICKET]
Here comes our here OC4J that, it seems, takes it as an offence that some referrer is going to some of its hosted applications with a Query String and a request parameter. So, very logically(!!!), the OC4J container just truncates the query string and this is how the CAS ticket gets lost in the midlle of nowhere. Thanks to Seyyed Jamal, a great friend, the idea is to pass the ticket through CAS using CLEAN URL's instead of query strings such as:
https://oc4japphost:8443/myapp/j_acegi_security_check/ticket/[CAS SERVICE TICKET]
This way the OC4J container is actually unaware of what's going on. To implement the solution:
  1. CAS login-webflow.xml should edited for external redirection after successful sing-in.
  2. Acegi's CasProcessingFilter need be edited for CAS ticket lookup based on clean URL's.
CAS: login-webflow.xml The end-state should be edited so that the value for its view would be:

<end-state id="redirect" view="externalRedirect:${externalContext.requestParameterMap['service']}${requestScope.ticket== null ? '' : '/ticket/' + requestScope.ticket}"></end-state>
Acegi: CasProcessingFilter
public Authentication attemptAuthentication(HttpServletRequest request) throws AuthenticationException {
        String username = CAS_STATEFUL_IDENTIFIER;
        String password = extractTicket(request);

        logger.warn("[ CUSTOMIZED OC4J CAS Processing Filter ] Found CAS ticket: " + password);

        if (password == null) {
            password = "";
        }
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));

        Authentication authenticationResult = this.getAuthenticationManager().authenticate(authRequest);

        if (authenticationResult != null) {
           logger.warn("[ CUSTOMIZED OC4J CAS Processing Filter ] CAS authentication completed. Its success will be decided afterwards.");
        }
        return authenticationResult;
    }
And, now:
    protected String extractTicket(HttpServletRequest request) {
        String ticket = request.getParameter("ticket");
        if (StringUtils.hasText(ticket)) {
            logger.warn("Service TICKET found on query string: " + ticket);
            return ticket;
        }
        String uri = request.getRequestURI();
        if (uri.indexOf("/ticket/") > 0) {
            ticket = uri.substring(uri.lastIndexOf('/') + 1);
            if (StringUtils.hasText(ticket)) {
                logger.warn("Service TICKET found on clean URL: " + ticket);
                return ticket;
            }
        }
        logger.error("No SERVICE TICKET FOUND on request: " + uri);
        return null;
    }
Then, remeber to edit you Acegi's bean of org.acegisecurity.util.FilterChainProxy and it property filterInvocationDefinitionSource so that the value for j_acegi_security_check would be:
/j_acegi_security_check*/**=httpSessionContextIntegrationFilter,casProcessingFilter
It seems that OC4J is working through this solution on separate CAS server and its applications get singed in through. This issue has also been discussed in Spring Forums: http://forum.springsource.org/showthread.php?t=38897 Hope this would help.

No comments:

Post a Comment