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@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(a)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();
+ }
+}