Hi there,

I post this to save others needless hours of gazing into the "void."

The customer requested that Single Sign On (SSO) be implemented.

(Note: for newbies like me, I had to do some research and found that what this means. Say like me, you have a cocoon application that is password protected and is on some server on the network. With SSO, once a user is logged into their machine, they do not need to retype their username/password when they access this application.)

There are many implementations and a large number of acronyms and it was all very confusing. But I found in the end, it was quite simple.

Let us say my Cocoon app is on a server called Cocoon_Server. Now if the enterprise has SSO, then they have a "Domain Server" that we query to find out if the user has the proper credentials. Lets say that the user is on a third machine called User_Computer.

There is a extremely useful resource http://spnego.sourceforge.net/ that provides everything you need with all the gobbledegook completely invisible to us.

Ideally, you would first implement the simple stand alone example that they provide to make sure that your SSO infrastructure and your development machine are setup correctly. (http://spnego.sourceforge.net/pre_flight.html). The one "GOTCHA" was that the is case sensitive and the documents at this link don't say that. You must follow the exact case that they show in their example.

Once you have your SSO development infrastructure setup correctly, its time to integrate it into Cocoon. I use flow script and Java, but there is a gotcha as at least in 2.1, cocoon.request returns a strange object that isn't quite compatible with HttpServletRequest and which is what you need for the spnego.jar file. To be able to do this, you need the ObjectModel passed to you and the easiest way I found was to do that using an "Action".

The first step is to put the spnego jar file from the site above into your lib directory. I put it in cocoon/lib/local though probably putting it in the lib directory for my app would have been the more correct choice.

Then write a Cocoon "Action" as follows:

1) Put an entry for your action in your "root" sitemap:
<map:actions>
<map:action name="ssologin" src="org.apache.cocoon.myapplication.bean.SpnegoCocoonAuthenticator"/>
</map:actions>


2) in your Login page's sitemap, add the following:

<map:match pattern="ssologin">
<map:act type ="ssologin">
<map:call function="{ssologin}"/>
</map:act>
</map:match>

3) Write your action as follows, modifying it if you need anything different:

package org.apache.cocoon.myapplication.bean;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.sourceforge.spnego.SpnegoAuthenticator;
import net.sourceforge.spnego.SpnegoHttpFilter.Constants;
import net.sourceforge.spnego.SpnegoHttpServletResponse;
import net.sourceforge.spnego.SpnegoPrincipal;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.ietf.jgss.GSSException;

import org.apache.cocoon.environment.http.HttpEnvironment;

import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.avalon.framework.parameters.Parameters;

import org.apache.cocoon.acting.Action;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.SourceResolver;

public class SpnegoCocoonAuthenticator implements Action, Initializable, ThreadSafe {
    private HttpSession session = null;
    protected SpnegoAuthenticator authenticator = null;

    public final void initialize() throws LifecycleException {

        final Map<String, String> map = new HashMap<String, String>();
        map.put(Constants.ALLOW_BASIC, "true");
        map.put("spnego.allow.localhost", "true");
        map.put("spnego.allow.unsecure.basic", "true");
        map.put("spnego.login.client.module", "spnego-client");
        map.put("spnego.krb5.conf", "krb5.conf");
        map.put("spnego.login.conf", "login.conf");
map.put("spnego.preauth.username", "Charlie_a_user_who_has_permissions_on_DOMAIN_SERVER");
        map.put("spnego.preauth.password", "StrongPassword");
        map.put("spnego.login.server.module", "spnego-server");
        map.put("spnego.prompt.ntlm", "true");
        map.put("spnego.allow.delegation", "true");
        map.put("spnego.logger.level", "1");

        try {
            authenticator = new SpnegoAuthenticator(map);
        } catch (LoginException e) {
            throw new LifecycleException(e);
        } catch (FileNotFoundException e) {
            throw new LifecycleException(e);
        } catch (GSSException e) {
            throw new LifecycleException(e);
        } catch (PrivilegedActionException e) {
            throw new LifecycleException(e);
        } catch (URISyntaxException e) {
            throw new LifecycleException(e);
        }
    }

public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String src, Parameters parameters) throws Exception {

final HttpServletRequest httpRequest = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT);

final SpnegoHttpServletResponse spnegoResponse = new SpnegoHttpServletResponse( (HttpServletResponse) (HttpServletResponse) objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT));

        final SpnegoPrincipal principal;

        try {
principal = this.authenticator.authenticate(httpRequest, spnegoResponse);

        } catch (GSSException e) {
            throw new IOException(e);
        }


//you now have the basic info.--if there is a valid name, the user is legit. //you can do more stuff if you need to like get her roles etc. but I didn't need any more.
        String userName = principal.getName();

        Map map = new HashMap(1);

        if(userName != null || !userName.equals(""))
            map.put("ssologin", "LoggedInScriptName");
        else
            map.put("ssologin", "InvalidUserScriptName");

        return map;
    }
}

4) Write a flowscript to handle "LoggedInScriptName" that puts the user right into the app as though she had typed the username/password. Also, to handle an invalid user, write the InvalidUserScriptName to handle the case of an invalid user.

That's it. Seems to work! I need to remove the username/pwd and other hardcoding and streamline it a bit, but the above is more or less all one needs.

Best.
Paul





---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to