Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

GTNSSO-29 Implement SPNEGO SSO for gatein-tomcat packaging

  • Loading branch information...
commit 1b7750ef9c71828c7113d403a29905dbb51e90d1 1 parent 137efe3
@nttuyen nttuyen authored mposolda committed
View
1  pom.xml
@@ -39,6 +39,7 @@
<module>josso</module>
<module>opensso</module>
<module>spnego</module>
+ <module>spnegosso</module>
<module>saml</module>
<module>packaging</module>
<module>integration</module>
View
43 spnegosso/README.md
@@ -0,0 +1,43 @@
+This is other spnego implementation beside https://github.com/gatein/gatein-sso/tree/master/spnego
+We introduce new spnego implementation because existing implementation dose not work on gatein tomcat packaging.
+This implementation work well on both jboss and tomcat packaging.
+
+###Build and configure
+
+1. Configure SPNEGO Server
+ You configure SPNEGO server follow the guideline at gatein document: https://docs.jboss.org/author/display/GTNPORTAL37/SPNEGO
+
+2. Build and deploy spnegosso
+ - Use maven to build gatein-spnego project
+ - Copy spnegosso-${VERSION}.jar to $GATEIN_TOMCAT/lib folder
+
+3. Configure gatein
+ - Append this login module configuration into $GATEIN_HOME/conf/jaas.conf
+```
+spnego-server {
+ com.sun.security.auth.module.Krb5LoginModule required
+ storeKey=true
+ doNotPrompt=true
+ useKeyTab=true
+ keyTab="/etc/krb5.keytab"
+ principal="HTTP/server.local.network@LOCAL.NETWORK"
+ useFirstPass=true
+ debug=true
+ isInitiator=false;
+};
+```
+
+ - Change SSO section in the file $GATEIN_HOME/gatein/conf/configuration.properties to be like this:
+```
+gatein.sso.enabled=true
+gatein.sso.filter.spnego.enabled=true
+gatein.sso.callback.enabled=false
+gatein.sso.skip.jsp.redirection=false
+gatein.sso.login.module.enabled=true
+gatein.sso.login.module.class=org.gatein.security.sso.spnego.SPNEGOSSOLoginModule
+gatein.sso.filter.login.sso.url=/@@portal.container.name@@/spnegosso
+gatein.sso.filter.initiatelogin.enabled=false
+gatein.sso.valve.enabled=false
+gatein.sso.filter.logout.enabled=false
+```
+
View
26 spnegosso/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.gatein.sso</groupId>
+ <artifactId>sso-parent</artifactId>
+ <relativePath>../pom.xml</relativePath>
+ <version>1.4.3.Final-SNAPSHOT</version>
+ </parent>
+ <artifactId>spnegosso</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.gatein.sso</groupId>
+ <artifactId>sso-agent</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
View
32 spnegosso/src/main/java/org/gatein/security/sso/spnego/SPNEGOSSOContext.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 eXo Platform SAS.
+ *
+ * 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.security.sso.spnego;
+
+import javax.servlet.http.HttpServletRequest;
+
+public class SPNEGOSSOContext {
+ private static ThreadLocal<HttpServletRequest> currentRequest = new ThreadLocal<HttpServletRequest>();
+
+ public static void setCurrentRequest(HttpServletRequest req) {
+ currentRequest.set(req);
+ }
+ public static HttpServletRequest getCurrentRequest() {
+ return currentRequest.get();
+ }
+}
View
245 spnegosso/src/main/java/org/gatein/security/sso/spnego/SPNEGOSSOFilter.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2012 eXo Platform SAS.
+ *
+ * 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.security.sso.spnego;
+
+import java.io.IOException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.UUID;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import org.gatein.common.logging.Logger;
+import org.gatein.common.logging.LoggerFactory;
+import org.gatein.common.util.Base64;
+import org.gatein.sso.agent.filter.api.AbstractSSOInterceptor;
+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;
+
+public class SPNEGOSSOFilter extends AbstractSSOInterceptor {
+ private static final Logger log = LoggerFactory.getLogger(AbstractSSOInterceptor.class);
+
+ private static final GSSManager MANAGER = GSSManager.getInstance();
+
+ private LoginContext loginContext;
+ private String[] patterns = {"/login", "/spnegosso"};
+ private String loginServletPath = "/login";
+ private String securityDomain = "spnego-server";
+
+ public SPNEGOSSOFilter() {}
+
+ @Override
+ protected void initImpl() {
+ String patternParam = this.getInitParameter("patterns");
+ if(patternParam != null && !patternParam.isEmpty()) {
+ this.patterns = patternParam.split(",");
+ }
+
+ String loginServlet = this.getInitParameter("loginServletPath");
+ if(loginServlet != null && !loginServlet.isEmpty()) {
+ this.loginServletPath = loginServlet;
+ }
+
+ String domain = this.getInitParameter("securityDomain");
+ if(domain != null && !domain.isEmpty()) {
+ this.securityDomain = domain;
+ }
+
+ try {
+ this.loginContext = new LoginContext(this.securityDomain);
+ } catch (LoginException ex) {
+ log.warn("Exception while init LoginContext, so SPNEGO SSO will not work", ex);
+ }
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ final HttpServletRequest req = (HttpServletRequest)request;
+ final HttpServletResponse resp = (HttpServletResponse)response;
+
+ //. Check if this is not spnego login request
+ if(!isSpnegoLoginRequest(req)) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ SPNEGOSSOContext.setCurrentRequest(req);
+ final String contextPath = req.getContextPath();
+ final String loginURI = contextPath + this.loginServletPath;
+ final String requestURI = req.getRequestURI();
+ String username = req.getParameter("username");
+ final String remoteUser = req.getRemoteUser();
+
+ if(username != null || remoteUser != null) {
+ if(!loginURI.equalsIgnoreCase(requestURI)) {
+ // Redirect to /login if current request is /spnegosso to avoid error 404
+ // when user access to /spnegosso?username=username or when loggedIn user access to /spengosso
+ StringBuilder login = new StringBuilder(loginURI);
+ if(req.getQueryString() != null) {
+ login.append("?").append(req.getQueryString());
+ }
+ resp.sendRedirect(login.toString());
+ } else {
+ chain.doFilter(req, resp);
+ }
+ return;
+ }
+
+ String principal = null;
+ final String auth = req.getHeader("Authorization");
+ if(auth != null) {
+ try {
+ principal = this.login(req, resp, auth);
+ } catch (Exception ex) {
+ log.error("Exception occur when trying to login with SPNEGO", ex);
+ }
+ }
+
+ if(principal != null && !principal.isEmpty()) {
+ username = principal.substring(0, principal.indexOf('@'));
+ // We don't need user password when he login using SSO (SPNEGO)
+ // But LoginServlet require password is not empty to call login action instead of display input form
+ // So, we need to generate a random password
+ String password = UUID.randomUUID().toString();
+
+ HttpSession session = req.getSession();
+ session.setAttribute("SPNEGO_PRINCIPAL", username);
+
+ StringBuilder login = new StringBuilder(loginURI)
+ .append("?username=")
+ .append(username)
+ .append("&password=")
+ .append(password);
+ String initURL = req.getParameter("initialURI");
+ if(initURL != null) {
+ login.append("&initialURI=").append(initURL);
+ }
+
+ resp.sendRedirect(login.toString());
+ } else {
+ if(!loginURI.equals(requestURI)) {
+ RequestDispatcher dispatcher = req.getRequestDispatcher("/login");
+ dispatcher.include(req, resp);
+ } else {
+ chain.doFilter(req, resp);
+ }
+ resp.setHeader("WWW-Authenticate", "Negotiate");
+ resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+ }
+
+ private boolean isSpnegoLoginRequest(HttpServletRequest request) {
+ final String uri = request.getRequestURI();
+ final String context = request.getContextPath();
+ for(String pattern : this.patterns) {
+ if(uri.equals(context.concat(pattern))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String login(HttpServletRequest req, HttpServletResponse resp, String auth) throws Exception {
+ if(this.loginContext == null) {
+ return null;
+ }
+ this.loginContext.login();
+
+ final String principal;
+ final String tok = auth.substring("Negotiate".length() + 1);
+ final byte[] gss = Base64.decode(tok);
+
+ GSSContext context = null;
+ byte[] token = null;
+ context = MANAGER.createContext(getServerCredential(loginContext.getSubject()));
+ token = context.acceptSecContext(gss, 0, gss.length);
+
+ if (null == token) {
+ return null;
+ }
+
+ resp.setHeader("WWW-Authenticate", "Negotiate" + ' ' + Base64.encodeBytes(token));
+
+ if (!context.isEstablished()) {
+ resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ return null;
+ }
+
+ principal = context.getSrcName().toString();
+ context.dispose();
+
+ this.loginContext.logout();
+
+ return principal;
+ }
+
+
+ /**
+ * Returns the {@link org.ietf.jgss.GSSCredential} the server uses for pre-authentication.
+ *
+ * @param subject account server uses for pre-authentication
+ * @return credential that allows server to authenticate clients
+ * @throws java.security.PrivilegedActionException
+ */
+ static GSSCredential getServerCredential(final Subject subject)
+ throws PrivilegedActionException {
+
+ final PrivilegedExceptionAction<GSSCredential> action =
+ new PrivilegedExceptionAction<GSSCredential>() {
+ public GSSCredential run() throws GSSException {
+ return MANAGER.createCredential(
+ null
+ , GSSCredential.INDEFINITE_LIFETIME
+ , getOid()
+ , GSSCredential.ACCEPT_ONLY);
+ }
+ };
+ return Subject.doAs(subject, action);
+ }
+
+ /**
+ * Returns the Universal Object Identifier representation of
+ * the SPNEGO mechanism.
+ *
+ * @return Object Identifier of the GSS-API mechanism
+ */
+ private static Oid getOid() {
+ Oid oid = null;
+ try {
+ oid = new Oid("1.3.6.1.5.5.2");
+ } catch (GSSException gsse) {
+ gsse.printStackTrace();
+ }
+ return oid;
+ }
+
+ @Override
+ public void destroy() {}
+}
View
103 spnegosso/src/main/java/org/gatein/security/sso/spnego/SPNEGOSSOLoginModule.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 eXo Platform SAS.
+ *
+ * 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.security.sso.spnego;
+
+import javax.security.auth.login.LoginException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import org.exoplatform.container.ExoContainer;
+import org.exoplatform.services.log.ExoLogger;
+import org.exoplatform.services.log.Log;
+import org.exoplatform.services.security.Authenticator;
+import org.exoplatform.services.security.Identity;
+import org.exoplatform.services.security.UsernameCredential;
+import org.exoplatform.services.security.jaas.AbstractLoginModule;
+
+public class SPNEGOSSOLoginModule extends AbstractLoginModule {
+ private static final Log log = ExoLogger.getLogger(SPNEGOSSOLoginModule.class);
+
+ public static final String OPTION_ENABLE_FALLBACK_FORM_AUTHENTICATION = "enableFormAuthentication";
+
+ @Override
+ protected Log getLogger() {
+ return log;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ try {
+ ExoContainer container = getContainer();
+
+ HttpServletRequest servletRequest = SPNEGOSSOContext.getCurrentRequest();
+ if (servletRequest == null) {
+ log.debug("HttpServletRequest is null. SPNEGOLoginModule will be ignored.");
+ return false;
+ }
+
+ HttpSession session = servletRequest.getSession();
+ String username = (String)session.getAttribute("SPNEGO_PRINCIPAL");
+ if(username != null) {
+ establishSecurityContext(container, username);
+ if (log.isTraceEnabled()) {
+ log.trace("Successfully established security context for user " + username);
+ }
+ return true;
+ }
+
+ } catch (Exception ex) {
+ log.error("Exception when trying to login with SPNEGO", ex);
+ }
+
+ // Disable fallback to FORM authentication
+ if("false".equalsIgnoreCase((String)this.options.get(OPTION_ENABLE_FALLBACK_FORM_AUTHENTICATION))) {
+ throw new LoginException("FORM authentication was disabled by SPNEGO login module.");
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ return true;
+ }
+
+ protected void establishSecurityContext(ExoContainer container, String username) throws Exception {
+ Authenticator authenticator = container.getComponentInstanceOfType(Authenticator.class);
+
+ if (authenticator == null) {
+ throw new LoginException("No Authenticator component found, check your configuration");
+ }
+
+ Identity identity = authenticator.createIdentity(username);
+
+ sharedState.put("exo.security.identity", identity);
+ sharedState.put("javax.security.auth.login.name", username);
+ subject.getPublicCredentials().add(new UsernameCredential(username));
+ }
+}
View
61 spnegosso/src/main/resources/conf/portal/configuration.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+
+ Copyright (C) 2009 eXo Platform SAS.
+
+ 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.
+
+-->
+
+<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.exoplaform.org/xml/ns/kernel_1_2.xsd http://www.exoplaform.org/xml/ns/kernel_1_2.xsd"
+ xmlns="http://www.exoplaform.org/xml/ns/kernel_1_2.xsd">
+ <external-component-plugins>
+ <target-component>org.gatein.sso.integration.SSOFilterIntegrator</target-component>
+ <component-plugin>
+ <name>InitiateLoginFilter</name>
+ <set-method>addPlugin</set-method>
+ <type>org.gatein.sso.integration.SSOFilterIntegratorPlugin</type>
+ <init-params>
+ <value-param>
+ <name>filterClass</name>
+ <value>org.gatein.security.sso.spnego.SPNEGOSSOFilter</value>
+ </value-param>
+ <value-param>
+ <name>enabled</name>
+ <value>${gatein.sso.filter.spnego.enabled:false}</value>
+ </value-param>
+ <value-param>
+ <name>filterMapping</name>
+ <value>/*</value>
+ </value-param>
+
+ <value-param>
+ <name>patterns</name>
+ <value>${gatein.sso.filter.spnego.pattern:/login,/spnegosso}</value>
+ </value-param>
+ <value-param>
+ <name>loginServletPath</name>
+ <value>${gatein.sso.filter.spnego.loginServletPath:/login}</value>
+ </value-param>
+ <value-param>
+ <name>securityDomain</name>
+ <value>${gatein.sso.filter.spnego.securityDomain:spnego-server}</value>
+ </value-param>
+ </init-params>
+ </component-plugin>
+ </external-component-plugins>
+</configuration>
Please sign in to comment.
Something went wrong with that request. Please try again.