Merged HEAD-BUG-FIX (5.1/Cloud) to HEAD (5.1/Cloud)

90918: MNT-12764 - The X-Alfresco-Remote-User (SsoUserHeader) SSO code path executes x2 requests and is stateful when it does not need to be
   Merged PROPERTY_GROUP_PROTOTYPING (5.0/Cloud) to HEAD-BUG-FIX (5.0/Cloud)
      90558: Refactoring of SSO paths
       - Added webscripts.authenticator.remoteuser to allow stateless auth of X-Alfresco-Remote-User via the /service servlet mapping. Falls back to basic auth as usual.
       - Refactored PublicApiAuthenticatorFactory to extend new RemoteUserAuthenticatorFactory to allow external auth to work correctly with Public API endpoint.
       - BasicHttpAuthenticatorFactory tweaked to allow sub-classes to use more of its beans and methods.
       - Related code clean up


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@94741 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2015-01-31 11:06:54 +00:00
parent 1f11c927f3
commit e9a5d1c670
5 changed files with 197 additions and 100 deletions

View File

@@ -75,6 +75,7 @@
<bean id="publicapi.authenticator" class="org.alfresco.rest.api.PublicApiAuthenticatorFactory"> <bean id="publicapi.authenticator" class="org.alfresco.rest.api.PublicApiAuthenticatorFactory">
<property name="authenticationService" ref="AuthenticationService" /> <property name="authenticationService" ref="AuthenticationService" />
<property name="authenticationListener" ref="webScriptAuthenticationListener"/> <property name="authenticationListener" ref="webScriptAuthenticationListener"/>
<property name="authenticationComponent" ref="authenticationComponent" />
<property name="transactionHelper" ref="web.retryingTransactionHelper" /> <property name="transactionHelper" ref="web.retryingTransactionHelper" />
<property name="tenantAuthentication" ref="publicapi.tenantAuthenticator"/> <property name="tenantAuthentication" ref="publicapi.tenantAuthenticator"/>
<property name="validAuthentictorKeys"> <property name="validAuthentictorKeys">

View File

@@ -183,13 +183,21 @@
<!-- Web Script Authenticators --> <!-- Web Script Authenticators -->
<!-- --> <!-- -->
<!-- HTTP Basic Authenticator (Servlet based) --> <!-- HTTP Basic Authenticator (Servlet based - stateless) -->
<bean id="webscripts.authenticator.basic" class="org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory"> <bean id="webscripts.authenticator.basic" class="org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory">
<property name="authenticationService" ref="AuthenticationService" /> <property name="authenticationService" ref="AuthenticationService" />
<property name="authenticationListener" ref="webScriptAuthenticationListener"/> <property name="authenticationListener" ref="webScriptAuthenticationListener"/>
</bean> </bean>
<!-- HTTP Basic Authenticator (Servlet based) for MT --> <!-- HTTP Remote User Authenticator (Servlet based - stateless) -->
<bean id="webscripts.authenticator.remoteuser" class="org.alfresco.repo.web.scripts.servlet.RemoteUserAuthenticatorFactory">
<property name="authenticationService" ref="AuthenticationService" />
<property name="authenticationComponent" ref="authenticationComponent" />
<property name="authenticationListener" ref="webScriptAuthenticationListener"/>
<property name="remoteUserMapper" ref="RemoteUserMapper" />
</bean>
<!-- HTTP Basic Authenticator (Servlet based - stateless) for MT -->
<bean id="webscripts.authenticator.basic.tenant" class="org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory"> <bean id="webscripts.authenticator.basic.tenant" class="org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory">
<property name="authenticationService" ref="AuthenticationService" /> <property name="authenticationService" ref="AuthenticationService" />
<property name="authenticationListener" ref="webScriptAuthenticationListener"/> <property name="authenticationListener" ref="webScriptAuthenticationListener"/>

View File

@@ -50,8 +50,8 @@ public class BasicHttpAuthenticatorFactory implements ServletAuthenticatorFactor
private static Log logger = LogFactory.getLog(BasicHttpAuthenticator.class); private static Log logger = LogFactory.getLog(BasicHttpAuthenticator.class);
// Component dependencies // Component dependencies
private AuthenticationService authenticationService; protected AuthenticationService authenticationService;
private AuthenticationListener listener; protected AuthenticationListener listener;
/** /**
@@ -103,12 +103,12 @@ public class BasicHttpAuthenticatorFactory implements ServletAuthenticatorFactor
public class BasicHttpAuthenticator implements Authenticator public class BasicHttpAuthenticator implements Authenticator
{ {
// dependencies // dependencies
private WebScriptServletRequest servletReq; protected WebScriptServletRequest servletReq;
private WebScriptServletResponse servletRes; protected WebScriptServletResponse servletRes;
private String authorization; protected String authorization;
private String ticket; protected String ticket;
private AuthenticationListener listener; protected AuthenticationListener listener;
/** /**
* Construct * Construct

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.web.scripts.servlet;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.web.auth.AuthenticationListener;
import org.alfresco.repo.web.auth.TicketCredentials;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.Authenticator;
import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse;
/**
* Authenticator to provide Remote User based Header authentication dropping back to Basic Auth otherwise.
* Statelessly authenticating via a secure header now does not require a Session so can be used with
* request-level load balancers which was not previously possible.
* <p>
* @see web-scripts-application-context.xml and web.xml - bean id 'webscripts.authenticator.remoteuser'
* <p>
* This authenticator can be bound to /service and does not require /wcservice (Session) mapping.
*
* @since 5.1
* @author Kevin Roast
*/
public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactory
{
private static Log logger = LogFactory.getLog(RemoteUserAuthenticatorFactory.class);
protected RemoteUserMapper remoteUserMapper;
protected AuthenticationComponent authenticationComponent;
public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper)
{
this.remoteUserMapper = remoteUserMapper;
}
public void setAuthenticationComponent(AuthenticationComponent authenticationComponent)
{
this.authenticationComponent = authenticationComponent;
}
@Override
public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
{
return new RemoteUserAuthenticator(req, res, this.listener);
}
/**
* Remote User authenticator - adds header authentication onto Basic Auth. Stateless does not require Session.
*
* @author Kevin Roast
*/
public class RemoteUserAuthenticator extends BasicHttpAuthenticator
{
public RemoteUserAuthenticator(WebScriptServletRequest req, WebScriptServletResponse res, AuthenticationListener listener)
{
super(req, res, listener);
}
@Override
public boolean authenticate(RequiredAuthentication required, boolean isGuest)
{
// retrieve the remote user if configured and available - authenticate that user directly
final String userId = getRemoteUser();
if (userId != null)
{
authenticationComponent.setCurrentUser(userId);
listener.userAuthenticated(new TicketCredentials(authenticationService.getCurrentTicket()));
return true;
}
else
{
return super.authenticate(required, isGuest);
}
}
/**
* Retrieve the remote user from servlet request header when using a secure connection.
* The RemoteUserMapper bean must be active and configured.
*
* @return remote user ID or null if not active or found
*/
protected String getRemoteUser()
{
String userId = null;
// If the remote user mapper is configured, we may be able to map in an externally authenticated user
if (remoteUserMapper != null &&
(!(remoteUserMapper instanceof ActivateableBean) || ((ActivateableBean) remoteUserMapper).isActive()))
{
userId = remoteUserMapper.getRemoteUser(this.servletReq.getHttpServletRequest());
}
if (logger.isDebugEnabled())
{
if (userId == null)
{
logger.debug("No external user ID in request.");
}
else
{
logger.debug("Extracted external user ID from request: " + userId);
}
}
return userId;
}
}
}

View File

@@ -1,3 +1,21 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.rest.api; package org.alfresco.rest.api;
import java.util.Collections; import java.util.Collections;
@@ -7,17 +25,15 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.web.auth.AuthenticationListener; import org.alfresco.repo.web.auth.AuthenticationListener;
import org.alfresco.repo.web.auth.TenantAuthentication; import org.alfresco.repo.web.auth.TenantAuthentication;
import org.alfresco.repo.web.auth.WebCredentials; import org.alfresco.repo.web.auth.WebCredentials;
import org.alfresco.repo.web.scripts.TenantWebScriptServletRequest; import org.alfresco.repo.web.scripts.TenantWebScriptServletRequest;
import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory; import org.alfresco.repo.web.scripts.servlet.RemoteUserAuthenticatorFactory;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.Authenticator; import org.springframework.extensions.webscripts.Authenticator;
@@ -33,21 +49,16 @@ import org.springframework.extensions.webscripts.servlet.WebScriptServletRespons
* *
* @author sglover * @author sglover
*/ */
public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory public class PublicApiAuthenticatorFactory extends RemoteUserAuthenticatorFactory
{ {
private static Log logger = LogFactory.getLog(PublicApiAuthenticatorFactory.class); private static Log logger = LogFactory.getLog(PublicApiAuthenticatorFactory.class);
public static final String DEFAULT_AUTHENTICATOR_KEY_HEADER = "X-Alfresco-Authenticator-Key"; public static final String DEFAULT_AUTHENTICATOR_KEY_HEADER = "X-Alfresco-Authenticator-Key";
public static final String DEFAULT_REMOTE_USER_HEADER = "X-Alfresco-Remote-User";
private String remoteUserHeader = DEFAULT_REMOTE_USER_HEADER;
private String authenticatorKeyHeader = DEFAULT_AUTHENTICATOR_KEY_HEADER; private String authenticatorKeyHeader = DEFAULT_AUTHENTICATOR_KEY_HEADER;
private RemoteUserMapper remoteUserMapper;
private RetryingTransactionHelper retryingTransactionHelper; private RetryingTransactionHelper retryingTransactionHelper;
private TenantAuthentication tenantAuthentication; private TenantAuthentication tenantAuthentication;
private Set<String> validAuthenticatorKeys = Collections.emptySet(); private Set<String> validAuthenticatorKeys = Collections.emptySet();
private AuthenticationListener authenticationListener;
private Set<String> outboundHeaderNames; private Set<String> outboundHeaderNames;
public void setAuthenticatorKeyHeader(String authenticatorKeyHeader) public void setAuthenticatorKeyHeader(String authenticatorKeyHeader)
@@ -55,11 +66,6 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
this.authenticatorKeyHeader = authenticatorKeyHeader; this.authenticatorKeyHeader = authenticatorKeyHeader;
} }
public void setAuthenticationListener(AuthenticationListener authenticationListener)
{
this.authenticationListener = authenticationListener;
}
/** /**
* Set the headers passed to the gateway for authentication. * Set the headers passed to the gateway for authentication.
* *
@@ -80,11 +86,6 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
this.outboundHeaderNames = outboundHeaders; this.outboundHeaderNames = outboundHeaders;
} }
public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper)
{
this.remoteUserMapper = remoteUserMapper;
}
public void setTenantAuthentication(TenantAuthentication service) public void setTenantAuthentication(TenantAuthentication service)
{ {
this.tenantAuthentication = service; this.tenantAuthentication = service;
@@ -135,11 +136,10 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
/** /**
* Public api authentication with additional tenant applicability check * Public api authentication with additional tenant applicability check
*/ */
public class PublicApiAuthenticator extends BasicHttpAuthenticator public class PublicApiAuthenticator extends RemoteUserAuthenticator
{ {
// dependencies // dependencies
private TenantWebScriptServletRequest servletReq; private TenantWebScriptServletRequest servletReq;
private WebScriptServletResponse servletRes;
// Proxy listener used to receive initial authentication events from the base BasicHttpAuthenticator // Proxy listener used to receive initial authentication events from the base BasicHttpAuthenticator
private ProxyListener proxyListener; private ProxyListener proxyListener;
@@ -159,41 +159,9 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
throw new WebScriptException("Request is not a tenant aware request"); throw new WebScriptException("Request is not a tenant aware request");
} }
servletReq = (TenantWebScriptServletRequest)req; servletReq = (TenantWebScriptServletRequest)req;
servletRes = res;
this.proxyListener = proxyListener; this.proxyListener = proxyListener;
} }
private String getRemoteUser()
{
String userId = null;
// If the remote user mapper is configured, we may be able to map in an externally authenticated user
if (remoteUserMapper != null
&& (!(remoteUserMapper instanceof ActivateableBean) || ((ActivateableBean) remoteUserMapper).isActive()))
{
userId = remoteUserMapper.getRemoteUser(this.servletReq.getHttpServletRequest());
}
else
{
// fall back to extracting the header
userId = servletReq.getHeader(remoteUserHeader);
}
if (logger.isDebugEnabled())
{
if (userId == null)
{
logger.debug("No external user ID in request.");
}
else
{
logger.debug("Extracted external user ID from request: " + userId);
}
}
return userId;
}
/* (non-Javadoc) /* (non-Javadoc)
* @see org.alfresco.web.scripts.Authenticator#authenticate(org.alfresco.web.scripts.Description.RequiredAuthentication, boolean) * @see org.alfresco.web.scripts.Authenticator#authenticate(org.alfresco.web.scripts.Description.RequiredAuthentication, boolean)
*/ */
@@ -204,34 +172,27 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
{ {
String authenticatorKey = servletReq.getHeader(authenticatorKeyHeader); String authenticatorKey = servletReq.getHeader(authenticatorKeyHeader);
String remoteUser = getRemoteUser(); String remoteUser = getRemoteUser();
if (authenticatorKey != null && if (authenticatorKey != null && remoteUser != null)
remoteUser != null)
{ {
// Trusted auth. Validate key and setup authentication context. // Trusted auth. Validate key and setup authentication context.
authorized = authenticateViaGateway(required, isGuest, authenticatorKey, remoteUser); authorized = authenticateViaGateway(required, isGuest, authenticatorKey, remoteUser);
} }
else else
{ {
// Fallback to standard BasicHttpAutheticator // Fallback to parent authenticator
try try
{ {
authorized = super.authenticate(required, isGuest); authorized = super.authenticate(required, isGuest);
} }
catch (AuthenticationException ae) catch (AuthenticationException ae)
{ {
// eg. guest // e.g. guest
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{
logger.debug("TenantBasicHttpAuthenticator: required="+required+", isGuest="+isGuest+" - "+ae.getMessage()); logger.debug("TenantBasicHttpAuthenticator: required="+required+", isGuest="+isGuest+" - "+ae.getMessage());
} }
} }
} if (authorized)
if (!authorized)
{ {
// not authorized, no point continuing
return authorized;
}
// check tenant validity // check tenant validity
final String tenant = servletReq.getTenant(); final String tenant = servletReq.getTenant();
final String email = AuthenticationUtil.getFullyAuthenticatedUser(); final String email = AuthenticationUtil.getFullyAuthenticatedUser();
@@ -249,15 +210,15 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
{ {
if (!authorized) if (!authorized)
{ {
authenticationListener.authenticationFailed(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials())); listener.authenticationFailed(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials()));
AuthenticationUtil.clearCurrentSecurityContext(); AuthenticationUtil.clearCurrentSecurityContext();
} }
else else
{ {
authenticationListener.userAuthenticated(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials())); listener.userAuthenticated(new TenantCredentials(tenant, email, proxyListener.getOrignalCredentials()));
}
} }
} }
return authorized; return authorized;
} }
finally finally
@@ -286,7 +247,6 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
return false; return false;
} }
} }
} }
private class ProxyListener implements AuthenticationListener private class ProxyListener implements AuthenticationListener
@@ -302,19 +262,18 @@ public class PublicApiAuthenticatorFactory extends BasicHttpAuthenticatorFactory
@Override @Override
public void authenticationFailed(WebCredentials credentials) public void authenticationFailed(WebCredentials credentials)
{ {
authenticationListener.authenticationFailed(credentials); listener.authenticationFailed(credentials);
} }
@Override @Override
public void authenticationFailed(WebCredentials credentials, Exception ex) public void authenticationFailed(WebCredentials credentials, Exception ex)
{ {
authenticationListener.authenticationFailed(credentials, ex); listener.authenticationFailed(credentials, ex);
} }
public WebCredentials getOrignalCredentials() public WebCredentials getOrignalCredentials()
{ {
return originalCredentials; return this.originalCredentials;
} }
} }
} }