[gatein-commits] gatein SVN: r7921 - in components/sso/trunk: agent/src/main/java/org/gatein/sso/agent/login and 1 other directories.

do-not-reply at jboss.org do-not-reply at jboss.org
Tue Nov 1 17:15:50 EDT 2011


Author: mposolda
Date: 2011-11-01 17:15:50 -0400 (Tue, 01 Nov 2011)
New Revision: 7921

Added:
   components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/FallbackFormJBossLoginModule.java
   components/sso/trunk/spnego/src/main/java/org/gatein/sso/spnego/NegotiationAuthenticator.java
Modified:
   components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGORolesModule.java
   components/sso/trunk/pom.xml
Log:
GTNPORTAL-2251 Fallback to FORM when SPNEGO not available or fails

Added: components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/FallbackFormJBossLoginModule.java
===================================================================
--- components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/FallbackFormJBossLoginModule.java	                        (rev 0)
+++ components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/FallbackFormJBossLoginModule.java	2011-11-01 21:15:50 UTC (rev 7921)
@@ -0,0 +1,68 @@
+/*
+ * JBoss, a division of Red Hat
+ * Copyright 2011, Red Hat Middleware, LLC, and individual
+ * contributors as indicated by the @authors tag. See the
+ * copyright.txt 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 org.exoplatform.services.security.j2ee.JbossLoginModule;
+import org.exoplatform.services.security.jaas.JAASGroup;
+import org.exoplatform.services.security.jaas.RolePrincipal;
+import org.exoplatform.services.security.jaas.UserPrincipal;
+
+import javax.security.auth.login.LoginException;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Set;
+
+/**
+ * This login module is used for SPNEGO integration. It is workaround, which returns only identity of user in method "commit()" and it does not return any groups.
+ * It is needed because {@link org.jboss.security.negotiation.spnego.SPNEGOLoginModule} assumes in method usernamePasswordLogin()
+ * that user identity is returned as first principal, which is not the case for JbossLoginModule. Issue is addressed in https://issues.jboss.org/browse/SECURITY-631
+ *
+ * @author <a href="mailto:mposolda at redhat.com">Marek Posolda</a>
+ */
+public class FallbackFormJBossLoginModule extends JbossLoginModule
+{
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public boolean commit() throws LoginException
+   {
+      if (super.commit())
+      {
+         Set<Principal> principals = subject.getPrincipals();
+
+         // clear existing principals from subject
+         principals.clear();
+
+         // add only username principal
+         principals.add(new UserPrincipal(identity.getUserId()));
+
+         return true;
+      }
+      else
+      {
+         return false;
+      }
+   }
+}

Modified: components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGORolesModule.java
===================================================================
--- components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGORolesModule.java	2011-11-01 19:24:13 UTC (rev 7920)
+++ components/sso/trunk/agent/src/main/java/org/gatein/sso/agent/login/SPNEGORolesModule.java	2011-11-01 21:15:50 UTC (rev 7921)
@@ -37,10 +37,12 @@
 import javax.security.auth.login.LoginException;
 import javax.security.jacc.PolicyContext;
 
+import javax.security.jacc.PolicyContextException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
 import org.jboss.security.SimpleGroup;
+import org.jboss.security.SimplePrincipal;
 import org.jboss.security.auth.spi.AbstractServerLoginModule;
 
 import org.exoplatform.container.ExoContainer;
@@ -219,81 +221,136 @@
             log.debug("Performing JBoss security manager cache eviction");
 
             ObjectName securityManagerName = new ObjectName("jboss.security:service=JaasSecurityManager");
-            
-            //Obtain the httpsession key
-            HttpServletRequest request = (HttpServletRequest) PolicyContext.getContext("javax.servlet.http.HttpServletRequest");
-            if(request == null)
-            {
-                return true;
-            }
-            
-            HttpSession session = request.getSession(false);
-            String sessionId = session.getId();
 
-            //
-            if (sessionId != null)
+           	String userName = null;
+            Principal principalToInvalidate = null;
+            String sessionId = null;
+
+            // If authentication was performed by Spnego, we have SimplePrincipal
+            Set<SimplePrincipal> simplePrincipals = subject.getPrincipals(SimplePrincipal.class);
+            if (!simplePrincipals.isEmpty())
             {
-            	String userName = null;
-	            Set<UserPrincipal> userPrincipals = subject.getPrincipals(UserPrincipal.class);
-	            if (!userPrincipals.isEmpty())
-	            {
-	               // There should be one
-	               userName = userPrincipals.iterator().next().getName();
-	            }
-	            
-              log.debug("Going to perform JBoss security manager cache eviction for user " + userName);
-
-               //
-               List allPrincipals =
-                  (List)jbossServer.invoke(securityManagerName, "getAuthenticationCachePrincipals",
-                     new Object[]{realmName}, new String[]{String.class.getName()});
-
-               // Make a copy to avoid some concurrent mods
-               allPrincipals = new ArrayList(allPrincipals);
-
-               // Lookup for invalidation key, it must be the same principal!
-               Principal key = null;
-               for (Iterator i = allPrincipals.iterator(); i.hasNext();)
+               // There should be one non-group principal
+               Iterator<SimplePrincipal> iterator = simplePrincipals.iterator();
+               while (iterator.hasNext())
                {
-                  Principal principal = (Principal)i.next();
-                  
-                  if (principal.getName().equals(sessionId))
+                  Principal temp = iterator.next();
+                  if (!(temp instanceof SimpleGroup))
                   {
-                     key = principal;
+                     principalToInvalidate = temp;
+                     userName = principalToInvalidate.getName();
+
+                     //Obtain the httpsession key
+                     sessionId = findSessionId();
                      break;
-                  }                  
+                  }
                }
-
-               // Perform invalidation
-               if (key != null)
+            }
+            // This means that authentication was performned by Form, we have UserPrincipal
+            else
+            {
+               Set<UserPrincipal> userPrincipals = subject.getPrincipals(UserPrincipal.class);
+               if (!userPrincipals.isEmpty())
                {
-                  jbossServer.invoke(securityManagerName, "flushAuthenticationCache", new Object[]{realmName, key},
-                     new String[]{String.class.getName(), Principal.class.getName()});
-                  log.debug("Performed JBoss security manager cache eviction for user " + sessionId + " with principal "
-                     + key);
+                  // There should be one
+                  principalToInvalidate = userPrincipals.iterator().next();
+                  userName = principalToInvalidate.getName();
                }
-               else
-               {
-                  log.warn("No principal found when performing JBoss security manager cache eviction for user "
-                     + userName);
-               }
             }
+
+            // Case with recursive call to 'logout' method
+            if (principalToInvalidate == null)
+            {
+               return true;
+            }
+
+            log.debug("Going to perform JBoss security manager cache eviction for user " + userName);
+
+            //
+            List allPrincipals =
+              (List)jbossServer.invoke(securityManagerName, "getAuthenticationCachePrincipals",
+                 new Object[]{realmName}, new String[]{String.class.getName()});
+
+            // Make a copy to avoid some concurrent mods
+            allPrincipals = new ArrayList(allPrincipals);
+
+            Principal key = findKeyPrincipal(principalToInvalidate, allPrincipals, sessionId);
+
+            // Perform invalidation
+            if (key != null)
+            {
+               jbossServer.invoke(securityManagerName, "flushAuthenticationCache", new Object[]{realmName, key},
+                  new String[]{String.class.getName(), Principal.class.getName()});
+               log.debug("Performed JBoss security manager cache eviction for user " + userName);
+            }
             else
             {
-               log.warn("No user name found when performing JBoss security manager cache eviction");
+               log.warn("No principal found when performing JBoss security manager cache eviction for user "
+                  + userName);
             }
          }
          catch (Exception e)
          {
-            log.debug("Could not perform JBoss security manager cache eviction", e);
+            log.warn("Could not perform JBoss security manager cache eviction", e);
          }
       }
       else
       {
-         log.debug("Could not find mbean server for performing JBoss security manager cache eviction");
+         log.warn("Could not find mbean server for performing JBoss security manager cache eviction");
       }
 
       //
       return true;
    }
+
+   private String findSessionId() throws PolicyContextException
+   {
+      HttpServletRequest request = (HttpServletRequest) PolicyContext.getContext("javax.servlet.http.HttpServletRequest");
+      if(request == null)
+      {
+         return null;
+      }
+
+      HttpSession session = request.getSession(false);
+      String sessionId = session.getId();
+      return sessionId;
+   }
+
+   private Principal findKeyPrincipal(Principal subjectPrincipal, List<Principal> allPrincipals, String sessionId)
+   {
+      Principal key = null;
+
+      // TODO: Investigate possibility to find principal without iteration through allPrincipals
+      // Spnego authentication case. Invalidation key starts with sessionId
+      if ((subjectPrincipal instanceof SimplePrincipal) && (sessionId != null))
+      {
+         for (Iterator i = allPrincipals.iterator(); i.hasNext();)
+         {
+            Principal principal = (Principal)i.next();
+
+            if (principal.getName().startsWith(sessionId))
+            {
+               key = principal;
+               break;
+            }
+         }
+      }
+      // Form authentication case
+      else
+      {
+         String userName = subjectPrincipal.getName();
+         for (Iterator i = allPrincipals.iterator(); i.hasNext();)
+         {
+            Principal principal = (Principal)i.next();
+
+            if (principal.getName().equals(userName))
+            {
+               key = principal;
+               break;
+            }
+         }
+      }
+
+      return key;
+   }
 }

Modified: components/sso/trunk/pom.xml
===================================================================
--- components/sso/trunk/pom.xml	2011-11-01 19:24:13 UTC (rev 7920)
+++ components/sso/trunk/pom.xml	2011-11-01 21:15:50 UTC (rev 7921)
@@ -79,7 +79,7 @@
 		<version.servlet-api>2.5</version.servlet-api>
 		
 		<!--  SPNEGO support using JBoss Negotiation -->
-		<version.jboss.negotiation>2.0.3.GA</version.jboss.negotiation>
+		<version.jboss.negotiation>2.1.0.GA</version.jboss.negotiation>
 	</properties>
 
 	<dependencyManagement>
@@ -220,10 +220,10 @@
 
 	<repositories>
 		<repository>
-			<id>repository.jboss.org</id>
+			<id>jboss-public-repository-group</id>
 			<name>JBoss Repository</name>
 			<layout>default</layout>
-			<url>http://repository.jboss.org/maven2/</url>
+			<url>https://repository.jboss.org/nexus/content/groups/public/</url>
 			<snapshots>
 				<enabled>false</enabled>
 			</snapshots>

Added: components/sso/trunk/spnego/src/main/java/org/gatein/sso/spnego/NegotiationAuthenticator.java
===================================================================
--- components/sso/trunk/spnego/src/main/java/org/gatein/sso/spnego/NegotiationAuthenticator.java	                        (rev 0)
+++ components/sso/trunk/spnego/src/main/java/org/gatein/sso/spnego/NegotiationAuthenticator.java	2011-11-01 21:15:50 UTC (rev 7921)
@@ -0,0 +1,336 @@
+/*
+ * JBoss, a division of Red Hat
+ * Copyright 2011, Red Hat Middleware, LLC, and individual
+ * contributors as indicated by the @authors tag. See the
+ * copyright.txt 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.spnego;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.Session;
+import org.apache.catalina.authenticator.AuthenticatorBase;
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.log4j.Logger;
+import org.jboss.security.negotiation.MessageFactory;
+import org.jboss.security.negotiation.NegotiationException;
+import org.jboss.security.negotiation.NegotiationMessage;
+import org.jboss.security.negotiation.common.MessageTrace;
+import org.jboss.security.negotiation.common.NegotiationContext;
+import org.jboss.util.Base64;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.security.Principal;
+
+import static org.apache.catalina.authenticator.Constants.SESS_PASSWORD_NOTE;
+import static org.apache.catalina.authenticator.Constants.SESS_USERNAME_NOTE;
+import static org.apache.catalina.authenticator.Constants.FORM_ACTION;
+import static org.apache.catalina.authenticator.Constants.FORM_PASSWORD;
+import static org.apache.catalina.authenticator.Constants.FORM_PRINCIPAL_NOTE;
+import static org.apache.catalina.authenticator.Constants.FORM_USERNAME;
+
+/**
+ * An authenticator to manage Negotiation based authentication in connection with the
+ * Negotiation login module. It's fork of {@link org.jboss.security.negotiation.NegotiationAuthenticator}, which is here
+ * to ensure backwards compatibility with JBoss 5 (jbossWeb 2)
+ *
+ * @author darran.lofthouse at jboss.com
+ * @version $Revision: 110643 $
+ */
+public class NegotiationAuthenticator extends FormAuthenticator
+{
+
+   private static final Logger log = Logger.getLogger(NegotiationAuthenticator.class);
+
+   private static final String NEGOTIATE = "Negotiate";
+
+   private static final String NEGOTIATION_CONTEXT = "NEGOTIATION_CONTEXT";
+
+   private static final String FORM_METHOD = "FORM";
+
+   protected String getNegotiateScheme()
+   {
+      return NEGOTIATE;
+   }
+
+   @Override
+   public boolean authenticate(final Request request, final HttpServletResponse response, final LoginConfig config)
+         throws IOException
+   {
+
+      boolean DEBUG = log.isDebugEnabled();
+      log.trace("Authenticating user");
+
+      Principal principal = request.getUserPrincipal();
+      if (principal != null)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Already authenticated '" + principal.getName() + "'");
+         return true;
+      }
+
+      String contextPath = request.getContextPath();
+      String requestURI = request.getDecodedRequestURI();
+      boolean loginAction = requestURI.startsWith(contextPath) && requestURI.endsWith(FORM_ACTION);
+      if (loginAction)
+      {
+         Realm realm = context.getRealm();
+         String username = request.getParameter(FORM_USERNAME);
+         String password = request.getParameter(FORM_PASSWORD);
+         principal = realm.authenticate(username, password);
+         if (principal == null)
+         {
+            RequestDispatcher disp = context.getServletContext().getRequestDispatcher(config.getErrorPage());
+            try
+            {
+               disp.forward(request.getRequest(), response);
+            }
+            catch (ServletException e)
+            {
+               IOException ex = new IOException("Unable to forward to error page.");
+               ex.initCause(e);
+
+               throw ex;
+            }
+            return false;
+         }
+
+         Session session = request.getSessionInternal();
+         requestURI = savedRequestURL(session);
+
+         session.setNote(FORM_PRINCIPAL_NOTE, principal);
+         session.setNote(SESS_USERNAME_NOTE, username);
+         session.setNote(SESS_PASSWORD_NOTE, password);
+
+         register(request, response, principal, FORM_METHOD, username, password);
+         response.sendRedirect(response.encodeRedirectURL(requestURI));
+
+         return false;
+      }
+
+      String negotiateScheme = getNegotiateScheme();
+
+      if (DEBUG)
+         log.debug("Header - " + request.getHeader("Authorization"));
+      String authHeader = request.getHeader("Authorization");
+      if (authHeader == null)
+      {
+
+         log.debug("No Authorization Header, initiating negotiation");
+         initiateNegotiation(request, response, config);
+
+         return false;
+      }
+      else if (authHeader.startsWith(negotiateScheme + " ") == false)
+      {
+         throw new IOException("Invalid 'Authorization' header.");
+      }
+
+      String authTokenBase64 = authHeader.substring(negotiateScheme.length() + 1);
+      byte[] authToken = Base64.decode(authTokenBase64);
+      ByteArrayInputStream authTokenIS = new ByteArrayInputStream(authToken);
+      MessageTrace.logRequestBase64(authTokenBase64);
+      MessageTrace.logRequestHex(authToken);
+
+      Session session = request.getSessionInternal();
+      NegotiationContext negotiationContext = (NegotiationContext) session.getNote(NEGOTIATION_CONTEXT);
+      if (negotiationContext == null)
+      {
+         log.debug("Creating new NegotiationContext");
+         negotiationContext = new NegotiationContext();
+         session.setNote(NEGOTIATION_CONTEXT, negotiationContext);
+      }
+
+      String username = negotiationContext.getUsername();
+      if (username == null || username.length() == 0)
+      {
+         username = session.getId() + "_" + String.valueOf(System.currentTimeMillis());
+         negotiationContext.setUsername(username);
+      }
+      String authenticationMethod = "";
+      try
+      {
+         // Set the ThreadLocal association.
+         negotiationContext.associate();
+
+         MessageFactory mf = MessageFactory.newInstance();
+         if (mf.accepts(authTokenIS) == false)
+         {
+            throw new IOException("Unsupported negotiation mechanism.");
+         }
+
+         NegotiationMessage requestMessage = mf.createMessage(authTokenIS);
+         negotiationContext.setRequestMessage(requestMessage);
+
+         Realm realm = context.getRealm();
+         principal = realm.authenticate(username, (String) null);
+
+         authenticationMethod = negotiationContext.getAuthenticationMethod();
+
+         if (DEBUG && principal != null)
+            log.debug("authenticated principal = " + principal);
+
+         NegotiationMessage responseMessage = negotiationContext.getResponseMessage();
+         if (responseMessage != null)
+         {
+            ByteArrayOutputStream responseMessageOS = new ByteArrayOutputStream();
+            responseMessage.writeTo(responseMessageOS, true);
+            String responseHeader = responseMessageOS.toString();
+
+            MessageTrace.logResponseBase64(responseHeader);
+
+            response.setHeader("WWW-Authenticate", negotiateScheme + " " + responseHeader);
+         }
+
+      }
+      catch (NegotiationException e)
+      {
+         IOException ioe = new IOException("Error processing " + negotiateScheme + " header.");
+         ioe.initCause(e);
+         throw ioe;
+      }
+      finally
+      {
+         // Clear the headers and remove the ThreadLocal association.
+         negotiationContext.clear();
+      }
+
+      if (principal == null)
+      {
+         response.sendError(Response.SC_UNAUTHORIZED);
+      }
+      else
+      {
+         register(request, response, principal, authenticationMethod, username, null);
+      }
+
+      return (principal != null);
+   }
+
+   /**
+    * Purpose of this method is backwards compatibility with JBoss 5.1
+    *
+    * @param request request
+    * @param response response
+    * @param config login configuration
+    * @return result of authentication
+    * @throws IOException
+    */
+   public boolean authenticate(final Request request, final Response response, final LoginConfig config)
+         throws IOException
+   {
+      return authenticate(request, (HttpServletResponse)response, config);
+   }
+
+   /**
+    * Purpose of this method is backwards compatibility with JBoss 5.1
+    *
+    * @param request request
+    * @param response response
+    * @param principal Principal to register
+    * @param authType authentication type (FORM, BASIC, SPNEGO, ...)
+    * @param username name of user
+    * @param password password of user
+    *
+    */
+   protected void register(Request request, HttpServletResponse response,
+                           Principal principal, String authType,
+                           String username, String password)
+   {
+      try
+      {
+         // first trying JBoss 6 signature register(Request, HttpServletResponse, Principal, String, String, String)
+         Method registerNewSignature = AuthenticatorBase.class.getDeclaredMethod("register", Request.class, HttpServletResponse.class, Principal.class, String.class, String.class, String.class);
+
+         // We have a method, so calling super
+         if (registerNewSignature != null)
+         {
+            super.register(request, response, principal, authType, username, password);
+         }
+      }
+      catch (NoSuchMethodException nsme)
+      {
+         // fallback to JBoss 5 signature register(Request, Response, Principal, String, String, String)
+         if (log.isDebugEnabled())
+         {
+            log.debug("Method 'register' with signature register(Request, HttpServletResponse, Principal, String, String, String) not found. Fallback to JBoss 5 signature register(Request, Response, Principal, String, String, String).");
+         }
+         try
+         {
+            Method registerOldSignature = AuthenticatorBase.class.getDeclaredMethod("register", Request.class, Response.class, Principal.class, String.class, String.class, String.class);
+            registerOldSignature.invoke(this, request, (Response)response, principal, authType, username, password);
+         }
+         catch (Exception e)
+         {
+            log.error(e);
+         }
+      }
+      catch (Exception e)
+      {
+         log.error(e);
+      }
+   }
+
+   private void initiateNegotiation(final Request request, final HttpServletResponse response, final LoginConfig config)
+         throws IOException
+   {
+      String loginPage = config.getLoginPage();
+      if (loginPage != null)
+      {
+         // TODO - Logic to cache and restore request.
+         ServletContext servletContext = context.getServletContext();
+         RequestDispatcher disp = servletContext.getRequestDispatcher(loginPage);
+
+         try
+         {
+            Session session = request.getSessionInternal();
+            saveRequest(request, session);
+
+            disp.include(request.getRequest(), response);
+            response.setHeader("WWW-Authenticate", getNegotiateScheme());
+            response.setStatus(Response.SC_UNAUTHORIZED);
+         }
+         catch (ServletException e)
+         {
+            IOException ex = new IOException("Unable to include loginPage");
+            ex.initCause(e);
+
+            throw ex;
+         }
+
+      }
+      else
+      {
+         response.setHeader("WWW-Authenticate", getNegotiateScheme());
+         response.sendError(Response.SC_UNAUTHORIZED);
+      }
+
+      response.flushBuffer();
+   }
+}



More information about the gatein-commits mailing list