spring-projects / spring-security-kerberos

Spring Security Kerberos
https://spring.io/projects/spring-security-kerberos
179 stars 224 forks source link

SES-11: Kerberos authentication for Client/Server apps (with HttpInvoker) #84

Open spring-projects-issues opened 15 years ago

spring-projects-issues commented 15 years ago

Mike Wiesner (Migrated from SES-11) said:

If you have a client running on an kerberized OS (like Windows), the client should automatically fetch the already present login from the OS and acquire a service ticket for your server and sends this as an HTTP Header to server.

The server then just uses the normal setup for a SPNEGO enabled web app.

That means, that one can also write a Client/Server app, without the need that a user has to login and you have a safe and performant way to transport this informatiion to the server.

spring-projects-issues commented 13 years ago

Borut Hadžialić said:

Hello,

I am implementing kerberized HttpInvoker communication for my current assignment.

So far I have a simple and crude implementation that works in simple tests (eg. few isolated requests after restart), and I have few dilemmas that I would like to solve before the project gets into heavier testing phases.

My biggest dilemma is should I try to add some kind of sessions that would be invisible to the user and that would reuse the GSSSession object or not. The session data on both the server and client would be some session id and the GSSSession object. Motivation to do this is to avoid creating a new context (with GSSContext initSecContext) for every request (although spnego in web environments seems to do this for every request?) Motivation to avoid creating a new context would be maybe performance reasons, and maybe to avoid having server leak memory because a context would be created for every incoming request and kept for some time? I am probably missing some conceptual knowledge here, this is my first time dealing with Kerberos..

More about this after the code example.

I'll post my current implementation here - hopefully there will be a constructive discussion that will clear things up a bit :D

My implementation also has the code that does delegated authentication, because what we need for our project is to propagate Single Sign On Kerberos credentials all the from the the client machine to the database (connection to the database must not be made trough a connection pool with single 'application' database user - it must be done using delegated kerberos credentials from the user that is using the client side app).

I tried using HTTP header 'Authorization: Negotiate [kerberosToken]' for HttpInvoker because I just finished running the examples from this blog post http://blog.springsource.com/2009/09/28/spring-security-kerberos/ (written by the same guy as assigne/reporter in this jira issue :D)
which set me up with a basic example infrastructure on the server side that I thought I could modify into something else.

[kerberosToken] that gets sent is http://en.wikipedia.org/wiki/Kerberos_%28protocol%29#Client_Service_Request and if I understand it correctly it serves 2 purposes: Identifies the client to the server Sets up a kerberos client/server session key (a symmetric cryptographic key)

The code.

On the client side currently I do this:

Pick up the Kerberos login from the client operating system (once, on app startup)

Use a custom CommonsHttpInvokerRequestExecutor to add a HTTP Authorization header to every request

Server side is pretty much the code from the mentioned blog post, modified to return user's delegated credentials (as javax.security.auth.Subject), not just username (as String).

Client side

/**
 * 'Picks up' client login from the client OS
 */
public class SubjectHolder implements InitializingBean {
    protected Subject subject;

    @Override
    public void afterPropertiesSet() throws Exception {
        try {
            LoginContext lc = new LoginContext("", null, null, new LoginConfig());
            lc.login();
            subject = lc.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Subject getSubject() {
        return subject;
    }

    private static class LoginConfig extends Configuration {

        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
            HashMap<String, String> options = new HashMap<String, String>();
            // options.put("useKeyTab", "true");

            // options.put("principal", "****");
            // options.put("storeKey", "true");
            // options.put("doNotPrompt", "true");

            // options.put("debug", "true");

            // options.put("isInitiator", "false");

            options.put("useTicketCache", "true");

            return new AppConfigurationEntry[] { new AppConfigurationEntry(
                    "com.sun.security.auth.module.Krb5LoginModule",
                    AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };
        }
    }

}
/**
 * Adds Http header 'Authentication: Negotiate [token]' to every HttpInvoker
 * request.
 */
public class SpnegoRequestExecutor extends org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor {
    protected SubjectHolder subjectHolder;

    protected String endpointSPN;

    protected GSSContext clientContext;

    public void ensureClientContext() {
        if (clientContext == null) {
            //OR clientContext expired or not ready
            try {
                GSSManager manager = GSSManager.getInstance();

                GSSName gssServerName = manager.createName(getEndpointSPN(), GSSName.NT_USER_NAME);

                Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2");

                clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid), spnegoMechOid, null,
                        GSSContext.DEFAULT_LIFETIME);

                //Turn on credential delegation
                clientContext.requestCredDeleg(true);

                System.out.println("clientContext: " + clientContext);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void setSubjectHolder(SubjectHolder subjectHolder) {
        this.subjectHolder = subjectHolder;
    }

    public SubjectHolder getSubjectHolder() {
        return subjectHolder;
    }

    public void setEndpointSPN(String endpointSPN) {
        this.endpointSPN = endpointSPN;
    }

    public String getEndpointSPN() {
        return endpointSPN;
    }

    protected PostMethod createPostMethod(HttpInvokerClientConfiguration config) {
        PostMethod postMethod = null;

        try {
            postMethod = super.createPostMethod(config);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //Add token
        try {
            ensureClientContext();
            Subject.doAs(subjectHolder.getSubject(), new AddSpnegoTokenAction(postMethod, clientContext));
            //TODO should I context.dispose()? at this point? memory leak?
            //clientContext.dispose();
            //clientContext = null;
            //??

        } catch (Exception e) {
            e.printStackTrace();
        }

        return postMethod;
    }

    protected class AddSpnegoTokenAction implements PrivilegedExceptionAction {
        protected PostMethod postMethod;
        protected GSSContext clientContext;

        public AddSpnegoTokenAction(PostMethod postMethod, GSSContext clientContext) {
            this.postMethod = postMethod;
            this.clientContext = clientContext;
        }

        public Object run() {
            try {

                //Create 'secContext' - returns the token for spnego 
                byte[] tokenForEndpoint = new byte[0];
                tokenForEndpoint = clientContext.initSecContext(tokenForEndpoint, 0, tokenForEndpoint.length);

                String negoHeader = "Negotiate " + new String(Base64.encode(tokenForEndpoint), "UTF-8");

                System.out.println(negoHeader);

                postMethod.addRequestHeader("Authorization", negoHeader);
            } catch (Exception e) {
                e.printStackTrace();
            }

            return "";
        }
    }

}

Server side

Pretty much code from http://blog.springsource.com/2009/09/28/spring-security-kerberos/ with a little change

private static class KerberosValidateAction implements PrivilegedExceptionAction<Subject> {
    byte[] kerberosTicket;

    public KerberosValidateAction(byte[] kerberosTicket) {
        this.kerberosTicket = kerberosTicket;
    }

    @Override
    public Subject run() throws Exception {
        GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
        context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
        String user = context.getSrcName().toString();

        //check if the credentials can be delegated  
        if (!context.getCredDelegState()) {
            System.out.println("*********************************");
            System.out.println("credentials can not be delegated!");
            System.out.println("*********************************");
            return null;
        }

        //get the delegated credentials from the calling peer...  
        GSSCredential clientCred = context.getDelegCred();

        //Create a Subject out of the delegated credentials.
        //With this Subject the application server can impersonate the client that sent the request. 
        Subject clientSubject = com.sun.security.jgss.GSSUtil.createSubject(context.getSrcName(), clientCred);

        //TODO should I context.dispose()? at this point? memory leak?
        //context.dispose();
        return clientSubject;
    }
}

Also, can someone please point me to some more up to date discussion on this topic if there is one - this issue is 1.5 years old now.

spring-projects-issues commented 13 years ago

Borut Hadžialić said:

Sorry for unformatted code - i though { code:java } macros worked here. Also the code is a bit messy (for example there should be no method SpnegoRequestExecutor.ensureClientContext - I'm sure that code belongs somewhere else), but I think it can be turned into a good and reusable framework class - after solving dilemmas and parametrizing it so it supports different needs.

In case get into problems while running it on windows xp (eg. KDC has no support for encryption type (14) ), try http://download.oracle.com/javase/1.5.0/docs/guide/security/jgss/tutorials/Troubleshooting.html - that solved the problems I had.

spring-projects-issues commented 12 years ago

Borut Hadžialić said:

I made an article with code examples that builds on http://blog.springsource.com/2009/09/28/spring-security-kerberos/ and deals with:

http://confluence.objectshapers.com/display/~borut/Articles

All feedback / questions is welcome.