Author: sohil.shah(a)jboss.com
Date: 2010-03-28 06:05:19 -0400 (Sun, 28 Mar 2010)
New Revision: 2374
Added:
components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGOLoginModule.java
Modified:
components/sso/trunk/agent/pom.xml
components/sso/trunk/pom.xml
Log:
Kerberos Support via SPNEGO protocol
Modified: components/sso/trunk/agent/pom.xml
===================================================================
--- components/sso/trunk/agent/pom.xml 2010-03-26 23:21:58 UTC (rev 2373)
+++ components/sso/trunk/agent/pom.xml 2010-03-28 10:05:19 UTC (rev 2374)
@@ -1,4 +1,5 @@
-<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
+<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.gatein.sso</groupId>
<artifactId>sso-parent</artifactId>
@@ -62,6 +63,11 @@
<groupId>org.josso</groupId>
<artifactId>josso-protocol-client</artifactId>
</dependency>
+
+ <dependency>
+ <groupId>org.jboss.security</groupId>
+ <artifactId>jboss-negotiation-spnego</artifactId>
+ </dependency>
</dependencies>
<build>
@@ -114,37 +120,37 @@
</profile>
<!--
- TODO: move this into packaging module
- -->
- <!--
- <profile> <id>josso-agent</id> <build> <plugins>
<plugin>
- <groupId>org.jvnet.maven-antrun-extended-plugin</groupId>
- <artifactId>maven-antrun-extended-plugin</artifactId> <executions>
- <execution> <id>josso-agent-package</id>
<phase>package</phase>
- <configuration> <tasks> <echo message="Preparing the JOSSO Agent
for GateIn" /> <delete dir="${basedir}/target/josso" /> <mkdir
dir="${basedir}/target/josso" /> <unjar
src="${settings.localRepository}/org/exoplatform/portal/exo.portal.web.portal/${org.exoplatform.portal.version}/exo.portal.web.portal-${org.exoplatform.portal.version}.war"
dest="${basedir}/target/josso/portal.war" overwrite="true" />
<copy todir="${basedir}/target/josso/portal.war"
overwrite="true"> <fileset
dir="${basedir}/src/main/config/josso/portal.war" /> </copy> <jar
destfile="${basedir}/target/josso/02portal.war"
basedir="${basedir}/target/josso/portal.war" /> </tasks>
- </configuration> <goals> <goal>run</goal> </goals>
</execution>
- <execution> <id>josso-agent-install</id>
<phase>install</phase>
- <configuration> <tasks> <echo message="Deploying the JOSSO Agent
for GateIn" /> <copy file="${basedir}/target/josso/02portal.war"
todir="${gatein.location}/deploy/gatein.ear" overwrite="true" />
<copy
file="${settings.localRepository}/org/gatein/sso/auth-callback/${project.version}/auth-callback-${project.version}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
-
- <copy
file="${settings.localRepository}/org/gatein/sso/agent/${project.version}/agent-${project.version}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
-
-
- <copy
file="${settings.localRepository}/org/josso/josso-agent/${version.josso}/josso-agent-${version.josso}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
- <copy
file="${settings.localRepository}/org/josso/josso-servlet-agent/${version.josso}/josso-servlet-agent-${version.josso}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
- <copy
file="${settings.localRepository}/org/josso/josso-agent-shared/${version.josso}/josso-agent-shared-${version.josso}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
- <copy
file="${settings.localRepository}/org/josso/josso-protocol-client/${version.josso}/josso-protocol-client-${version.josso}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
- <copy
file="${settings.localRepository}/org/josso/josso-common/${version.josso}/josso-common-${version.josso}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
-
- <copy
file="${settings.localRepository}/org/josso/josso-core/${version.josso}/josso-core-${version.josso}.jar"
todir="${gatein.location}/deploy/gatein.ear/lib" overwrite="true"
/>
- </tasks> </configuration> <goals> <goal>run</goal>
</goals>
- </execution> </executions> </plugin> </plugins> </build>
</profile>
- -->
+ Note: This profile is used only during Development for deploying artifacts into a
Development Gatein server
+ -->
+ <profile>
+ <id>dev</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>dev</id>
+ <phase>install</phase>
+ <configuration>
+ <tasks>
+ <echo message="Deploying the Gatein SSO Agent" />
+
+ <copy
+ file="${settings.localRepository}/org/gatein/sso/sso-agent/${project.version}/${project.artifactId}-${project.version}.jar"
+ todir="${gatein.location}/deploy/gatein.ear/lib"
overwrite="true" />
+ </tasks>
+ </configuration>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
Added:
components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGOLoginModule.java
===================================================================
---
components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGOLoginModule.java
(rev 0)
+++
components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGOLoginModule.java 2010-03-28
10:05:19 UTC (rev 2374)
@@ -0,0 +1,473 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ *
+ * Copyright 2007, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.gatein.sso.agent.login;
+
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.acl.Group;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.Oid;
+import org.jboss.security.SimpleGroup;
+import org.jboss.security.auth.spi.AbstractServerLoginModule;
+import org.jboss.security.negotiation.NegotiationMessage;
+import org.jboss.security.negotiation.common.NegotiationContext;
+import org.jboss.security.negotiation.spnego.encoding.NegTokenInit;
+import org.jboss.security.negotiation.spnego.encoding.NegTokenTarg;
+import org.jboss.security.negotiation.spnego.encoding.SPNEGOMessage;
+
+import org.exoplatform.container.ExoContainer;
+import org.exoplatform.container.ExoContainerContext;
+import org.exoplatform.container.PortalContainer;
+import org.exoplatform.container.RootContainer;
+import org.exoplatform.services.security.Identity;
+import org.exoplatform.services.security.Authenticator;
+import org.exoplatform.services.security.IdentityRegistry;
+import org.exoplatform.services.security.jaas.UserPrincipal;
+
+/**
+ * Login module to work in conjunction with SPNEGOAuthenticator to handle the
+ * authentication requirements.
+ *
+ * @author darran.lofthouse(a)jboss.com
+ * @version $Revision: 83572 $
+ */
+public class SPNEGOLoginModule extends AbstractServerLoginModule
+{
+
+ private static final String SPNEGO = "SPNEGO";
+
+ private static final Oid kerberos;
+
+ // TODO - Pick a name for a default domain?
+ private String serverSecurityDomain;
+
+ private LoginContext serverLoginContext = null;
+
+ private Principal identity = null;
+
+ // GateIn integration
+ private static final String OPTION_PORTAL_CONTAINER_NAME =
"portalContainerName";
+ private static final String OPTION_REALM_NAME = "realmName";
+ private String portalContainerName;
+ private String realmName;
+
+ static
+ {
+ try
+ {
+ kerberos = new Oid("1.2.840.113554.1.2.2");
+ }
+ catch (GSSException e)
+ {
+ throw new RuntimeException("Unable to initialise Oid", e);
+ }
+ }
+
+ @Override
+ public void initialize(final Subject subject,
+ final CallbackHandler callbackHandler, final Map sharedState,
+ final Map options)
+ {
+ super.initialize(subject, callbackHandler, sharedState, options);
+
+ // Which security domain to authenticate the server.
+ serverSecurityDomain = (String) options.get("serverSecurityDomain");
+ log.debug("serverSecurityDomain=" + serverSecurityDomain);
+
+ // GateIn integration
+ this.portalContainerName = getPortalContainerName(options);
+ this.realmName = getRealmName(options);
+ }
+
+ @Override
+ public boolean login() throws LoginException
+ {
+ if (super.login() == true)
+ {
+ log.debug("super.login()==true");
+ return true;
+ }
+
+ super.loginOk = false;
+
+ NegotiationContext negotiationContext = NegotiationContext
+ .getCurrentNegotiationContext();
+ NegotiationMessage requestMessage = negotiationContext.getRequestMessage();
+ if (requestMessage instanceof SPNEGOMessage == false)
+ {
+ String message = "Unsupported negotiation mechanism '"
+ + requestMessage.getMessageType() + "'.";
+ log.warn(message);
+ throw new LoginException(message);
+ }
+
+ try
+ {
+ Subject server = getServerSubject();
+ AcceptSecContext action = new AcceptSecContext(negotiationContext);
+ Object result = Subject.doAs(server, action);
+
+ log.trace("Result - " + result);
+
+ if (result instanceof Boolean)
+ {
+ if (Boolean.TRUE.equals(result))
+ {
+ super.loginOk = true;
+ if (getUseFirstPass() == true)
+ {
+ String userName = identity.getName();
+ log.debug("Storing username '" + userName + "' and empty
password");
+ // Add the username and a null password to the shared state map
+ sharedState.put("javax.security.auth.login.name", identity);
+ sharedState.put("javax.security.auth.login.password", "");
+ }
+ }
+ }
+ else if (result instanceof Exception)
+ {
+ Exception e = (Exception) result;
+ log.error("Unable to authenticate", e);
+ throw new LoginException("Unable to authenticate - " + e.getMessage());
+ }
+
+ }
+ finally
+ {
+ if (serverLoginContext != null)
+ {
+ // TODO - We may not actually want to logout as if we use cache this may
+ // clear it,
+ serverLoginContext.logout();
+ }
+ }
+
+ log.trace("super.loginOk " + super.loginOk);
+ if (super.loginOk == true)
+ {
+ return true;
+ }
+ else
+ {
+ throw new LoginException("Continuation Required.");
+ }
+
+ }
+
+ @Override
+ protected Principal getIdentity()
+ {
+ return identity;
+ }
+
+ @Override
+ protected Group[] getRoleSets() throws LoginException
+ {
+ try
+ {
+ Authenticator authenticator = (Authenticator) getContainer()
+ .getComponentInstanceOfType(Authenticator.class);
+
+ String username = this.getIdentity().getName();
+ Identity identity = authenticator.createIdentity(username);
+
+ Group roles = new SimpleGroup("Roles");
+ Group callerPrincipal = new SimpleGroup("CallerPrincipal");
+ for (String role : identity.getRoles())
+ {
+ roles.addMember(this.createIdentity(role));
+ }
+
+ Group[] groups = { roles, callerPrincipal };
+ callerPrincipal.addMember(getIdentity());
+
+ return groups;
+ }
+ catch(Exception e)
+ {
+ throw new LoginException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean commit() throws LoginException
+ {
+ if (super.commit())
+ {
+ try
+ {
+ Authenticator authenticator = (Authenticator) getContainer()
+ .getComponentInstanceOfType(Authenticator.class);
+
+ IdentityRegistry identityRegistry =
+
(IdentityRegistry)getContainer().getComponentInstanceOfType(IdentityRegistry.class);
+
+ //TODO: Add check for single check
+
+ String username = this.getIdentity().getName();
+ Identity identity = authenticator.createIdentity(username);
+ identity.setSubject(this.subject);
+ identityRegistry.register(identity);
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ throw new LoginException(e.getMessage());
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ protected Subject getServerSubject() throws LoginException
+ {
+ LoginContext lc = new LoginContext(serverSecurityDomain);
+ lc.login();
+ // Cache so we can log out.
+ serverLoginContext = lc;
+
+ Subject serverSubject = serverLoginContext.getSubject();
+ log.debug("Subject = " + serverSubject);
+ log.debug("Logged in '" + serverSecurityDomain + "'
LoginContext");
+
+ return serverSubject;
+ }
+
+ private class AcceptSecContext implements PrivilegedAction
+ {
+
+ private final NegotiationContext negotiationContext;
+
+ public AcceptSecContext(final NegotiationContext negotiationContext)
+ {
+ this.negotiationContext = negotiationContext;
+ }
+
+ public Object run()
+ {
+ try
+ {
+ // The message type will have already been checked before this point so
+ // we know it is
+ // a SPNEGO message.
+ NegotiationMessage requestMessage = negotiationContext
+ .getRequestMessage();
+
+ // TODO - Ensure no way to fall through with gssToken still null.
+ byte[] gssToken = null;
+ if (requestMessage instanceof NegTokenInit)
+ {
+ NegTokenInit negTokenInit = (NegTokenInit) requestMessage;
+ List<Oid> mechList = negTokenInit.getMechTypes();
+
+ if (mechList.get(0).equals(kerberos))
+ {
+ gssToken = negTokenInit.getMechToken();
+ }
+ else
+ {
+ boolean kerberosSupported = false;
+
+ Iterator<Oid> it = mechList.iterator();
+ while (it.hasNext() && kerberosSupported == false)
+ {
+ kerberosSupported = it.next().equals(kerberos);
+ }
+
+ NegTokenTarg negTokenTarg = new NegTokenTarg();
+
+ if (kerberosSupported)
+ {
+ negTokenTarg.setNegResult(NegTokenTarg.ACCEPT_INCOMPLETE);
+ negTokenTarg.setSupportedMech(kerberos);
+ }
+ else
+ {
+ negTokenTarg.setNegResult(NegTokenTarg.REJECTED);
+ }
+ negotiationContext.setResponseMessage(negTokenTarg);
+
+ return Boolean.FALSE;
+ }
+
+ }
+ else if (requestMessage instanceof NegTokenTarg)
+ {
+ NegTokenTarg negTokenTarg = (NegTokenTarg) requestMessage;
+
+ gssToken = negTokenTarg.getResponseToken();
+ }
+
+ Object schemeContext = negotiationContext.getSchemeContext();
+ if (schemeContext != null
+ && schemeContext instanceof GSSContext == false)
+ {
+ throw new IllegalStateException(
+ "The schemeContext is not a GSSContext");
+ }
+
+ GSSContext gssContext = (GSSContext) schemeContext;
+ if (gssContext == null)
+ {
+ log.debug("Creating new GSSContext.");
+ GSSManager manager = GSSManager.getInstance();
+ gssContext = manager.createContext((GSSCredential) null);
+
+ negotiationContext.setSchemeContext(gssContext);
+ }
+
+ if (gssContext.isEstablished())
+ {
+ log
+ .warn("Authentication was performed despite already being
authenticated!");
+
+ // TODO - Refactor to only do this once.
+ // identity = new
+ // KerberosPrincipal(gssContext.getSrcName().toString());
+ identity = new UserPrincipal(mapSpnegoToGateIn(gssContext.getSrcName()
+ .toString()));
+
+ log.debug("context.getCredDelegState() = "
+ + gssContext.getCredDelegState());
+ log.debug("context.getMutualAuthState() = "
+ + gssContext.getMutualAuthState());
+ log.debug("context.getSrcName() = "
+ + gssContext.getSrcName().toString());
+
+ negotiationContext.setAuthenticationMethod(SPNEGO);
+ negotiationContext.setAuthenticated(true);
+
+ return Boolean.TRUE;
+ }
+
+ byte[] respToken = gssContext.acceptSecContext(gssToken, 0,
+ gssToken.length);
+
+ if (respToken != null)
+ {
+ NegTokenTarg negTokenTarg = new NegTokenTarg();
+ negTokenTarg.setResponseToken(respToken);
+
+ negotiationContext.setResponseMessage(negTokenTarg);
+ }
+
+ if (gssContext.isEstablished() == false)
+ {
+ return Boolean.FALSE;
+ }
+ else
+ {
+ // identity = new
+ // KerberosPrincipal(gssContext.getSrcName().toString());
+ identity = new UserPrincipal(mapSpnegoToGateIn(gssContext.getSrcName()
+ .toString()));
+
+ log.debug("context.getCredDelegState() = "
+ + gssContext.getCredDelegState());
+ log.debug("context.getMutualAuthState() = "
+ + gssContext.getMutualAuthState());
+ log.debug("context.getSrcName() = "
+ + gssContext.getSrcName().toString());
+
+ // TODO - Get these two in synch - maybe isAuthenticated based on an
+ // authentication method been set?
+ negotiationContext.setAuthenticationMethod(SPNEGO);
+ negotiationContext.setAuthenticated(true);
+ return Boolean.TRUE;
+ }
+
+ }
+ catch (Exception e)
+ {
+ return e;
+ }
+
+ }
+ }
+
+ private String mapSpnegoToGateIn(String spnegoPrincipal)
+ {
+ String exoPrincipal = spnegoPrincipal;
+
+ int indexOf = exoPrincipal.indexOf('@');
+ if (indexOf != -1)
+ {
+ exoPrincipal = exoPrincipal.substring(0, indexOf).trim();
+ }
+
+ return exoPrincipal;
+ }
+
+ private String getPortalContainerName(Map options)
+ {
+ if (options != null)
+ {
+ String optionValue = (String) options.get(OPTION_PORTAL_CONTAINER_NAME);
+ if (optionValue != null && optionValue.length() > 0)
+ {
+ return optionValue;
+ }
+ }
+ return PortalContainer.DEFAULT_PORTAL_CONTAINER_NAME;
+ }
+
+ private String getRealmName(Map options)
+ {
+ if (options != null)
+ {
+ String optionValue = (String) options.get(OPTION_REALM_NAME);
+ if (optionValue != null && optionValue.length() > 0)
+ {
+ return optionValue;
+ }
+ }
+ return PortalContainer.DEFAULT_REALM_NAME;
+ }
+
+ private ExoContainer getContainer() throws Exception
+ {
+ // TODO set correct current container
+ ExoContainer container = ExoContainerContext.getCurrentContainer();
+ if (container instanceof RootContainer)
+ {
+ container = RootContainer.getInstance().getPortalContainer(
+ portalContainerName);
+ }
+ return container;
+ }
+}
Modified: components/sso/trunk/pom.xml
===================================================================
--- components/sso/trunk/pom.xml 2010-03-26 23:21:58 UTC (rev 2373)
+++ components/sso/trunk/pom.xml 2010-03-28 10:05:19 UTC (rev 2374)
@@ -76,6 +76,9 @@
<!-- servlet-api -->
<version.servlet-api>2.5</version.servlet-api>
+
+ <!-- SPNEGO support using JBoss Negotiation -->
+ <version.jboss.negotiation>2.0.3.GA</version.jboss.negotiation>
</properties>
<dependencyManagement>
@@ -198,6 +201,13 @@
<artifactId>servlet-api</artifactId>
<version>${version.servlet-api}</version>
</dependency>
+
+ <!-- SPNEGO support using JBoss Negotiation -->
+ <dependency>
+ <groupId>org.jboss.security</groupId>
+ <artifactId>jboss-negotiation-spnego</artifactId>
+ <version>${version.jboss.negotiation}</version>
+ </dependency>
</dependencies>
</dependencyManagement>