[java-identity-provider] branch master updated: IDP-1502 - Avoid object creation in the external web flows

Scott Cantor cantor.2 at osu.edu
Wed Sep 25 15:12:42 EDT 2019


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=f8368495e5088c9823b80d8a3cb26d456db8fffb

The following commit(s) were added to refs/heads/master by this push:
       new  f836849   IDP-1502 - Avoid object creation in the external web flows
f836849 is described below

commit f8368495e5088c9823b80d8a3cb26d456db8fffb
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Sep 25 15:12:39 2019 -0400

    IDP-1502 - Avoid object creation in the external web flows
    
    https://issues.shibboleth.net/jira/browse/IDP-1502
    
    Adapt external authentication flow design.
---
 .../idp/authn/ExternalAuthentication.java          | 131 +++++--
 .../context/ExternalAuthenticationContext.java     |  25 +-
 .../idp/authn/impl/ExternalAuthenticationImpl.java |  60 +--
 .../impl/ValidateExternalAuthenticationTest.java   |  28 +-
 .../spnego/impl/SPNEGOAuthnControllerTest.java     | 436 ++++++++++-----------
 .../system/flows/authn/external-authn-flow.xml     |   3 +-
 .../system/flows/authn/remoteuser-authn-flow.xml   |   4 +-
 .../system/flows/authn/spnego-authn-flow.xml       |   3 +-
 .../system/flows/authn/x509-authn-flow.xml         |   3 +-
 .../profile/interceptor/ExternalInterceptor.java   |   7 +-
 10 files changed, 366 insertions(+), 334 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java
index 77de137..a609d23 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/ExternalAuthentication.java
@@ -24,17 +24,29 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.context.ExternalContextHolder;
+import org.springframework.webflow.context.servlet.ServletExternalContext;
+import org.springframework.webflow.execution.FlowExecution;
+import org.springframework.webflow.execution.repository.FlowExecutionRepository;
+import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException;
+import org.springframework.webflow.executor.FlowExecutorImpl;
 
+import com.google.common.base.Strings;
 import com.google.common.net.UrlEscapers;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /** Public interface supporting external authentication outside the webflow engine. */
-public class ExternalAuthentication {
+public abstract class ExternalAuthentication {
 
-    /** Parameter supplied to identify the per-conversation structure in the session. */
+    /** Parameter supplied to locate the SWF object needed in the servlet context. */
+    @Nonnull @NotEmpty public static final String SWF_KEY = "net.shibboleth.idp.flowExecutor";
+
+    /** Parameter supplied to identify the per-conversation parameter. */
     @Nonnull @NotEmpty public static final String CONVERSATION_KEY = "conversation";
     
     /** Request attribute to which user's principal should be bound. */
@@ -144,18 +156,16 @@ public class ExternalAuthentication {
      */
     @Nonnull @NotEmpty public static String startExternalAuthentication(@Nonnull final HttpServletRequest request)
             throws ExternalAuthenticationException {
-        final String conv = request.getParameter(CONVERSATION_KEY);
-        if (conv == null || conv.isEmpty()) {
+        final String key = request.getParameter(CONVERSATION_KEY);
+        if (Strings.isNullOrEmpty(key)) {
             throw new ExternalAuthenticationException("No conversation key found in request");
         }
+
+        final ProfileRequestContext profileRequestContext = getProfileRequestContext(key, request);
+        final ExternalAuthenticationContext extContext = getExternalAuthenticationContext(profileRequestContext);
+        extContext.getExternalAuthentication().doStart(request, profileRequestContext, extContext);
         
-        final Object obj = request.getSession().getAttribute(CONVERSATION_KEY + conv);
-        if (obj == null || !(obj instanceof ExternalAuthentication)) {
-            throw new ExternalAuthenticationException("No conversation state found in session for key (" + conv + ")");
-        }
-        
-        ((ExternalAuthentication) obj).doStart(request);
-        return conv;
+        return key;
     }
     
     /**
@@ -174,14 +184,9 @@ public class ExternalAuthentication {
             @Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response)
             throws ExternalAuthenticationException, IOException {
         
-        final Object obj = request.getSession().getAttribute(CONVERSATION_KEY + key);
-        if (obj == null || !(obj instanceof ExternalAuthentication)) {
-            throw new ExternalAuthenticationException("No conversation state found in session for key (" + key + ")");
-        }
-        
-        request.getSession().removeAttribute(CONVERSATION_KEY + key);
-        
-        ((ExternalAuthentication) obj).doFinish(request, response);
+        final ProfileRequestContext profileRequestContext = getProfileRequestContext(key, request);
+        final ExternalAuthenticationContext extContext = getExternalAuthenticationContext(profileRequestContext);
+        extContext.getExternalAuthentication().doFinish(request, response, profileRequestContext, extContext);
     }
 
     /**
@@ -196,12 +201,59 @@ public class ExternalAuthentication {
     @Nonnull public static ProfileRequestContext getProfileRequestContext(@Nonnull @NotEmpty final String key,
             @Nonnull final HttpServletRequest request) throws ExternalAuthenticationException {
         
-        final Object obj = request.getSession().getAttribute(CONVERSATION_KEY + key);
-        if (obj == null || !(obj instanceof ExternalAuthentication)) {
-            throw new ExternalAuthenticationException("No conversation state found in session");
+        final Object obj = request.getServletContext().getAttribute(SWF_KEY);
+        if (!(obj instanceof FlowExecutorImpl)) {
+            // This is a testing hook for injecting the PRC directly.
+            if (obj instanceof ProfileRequestContext) {
+                return (ProfileRequestContext) obj;
+            }
+            throw new ExternalAuthenticationException("No FlowExecutor available in servlet context");
+        }
+
+        try {
+            final FlowExecutionRepository repo = ((FlowExecutorImpl) obj).getExecutionRepository();
+            ExternalContextHolder.setExternalContext(
+                    new ServletExternalContext(request.getServletContext(), request, null));
+            
+            final FlowExecution execution = repo.getFlowExecution(repo.parseFlowExecutionKey(key));
+            final Object prc = execution.getConversationScope().get(ProfileRequestContext.BINDING_KEY);
+            if (!(prc instanceof ProfileRequestContext)) {
+                throw new ExternalAuthenticationException(
+                        "ProfileRequestContext not available in webflow conversation scope");
+            }
+            
+            return (ProfileRequestContext) prc;
+        } catch (final FlowExecutionRepositoryException e) {
+            throw new ExternalAuthenticationException("Error retrieving flow conversation", e);
+        } finally {
+            ExternalContextHolder.setExternalContext(null);
+        }
+    }
+    
+    /**
+     * Utility method to access the {@link ExternalAuthenticationContext}.
+     * 
+     * @param profileRequestContext profile request context
+     * 
+     * @return the {@link ExternalAuthenticationContext} to operate on
+     * 
+     * @throws ExternalAuthenticationException if the context is missing
+     */
+    @Nonnull private static ExternalAuthenticationContext getExternalAuthenticationContext(
+            @Nonnull final ProfileRequestContext profileRequestContext) throws ExternalAuthenticationException {
+        
+        final AuthenticationContext authContext = profileRequestContext.getSubcontext(AuthenticationContext.class);
+        if (authContext == null) {
+            throw new ExternalAuthenticationException("No AuthenticationContext found");
         }
         
-        return ((ExternalAuthentication) obj).getProfileRequestContext(request);
+        
+        final ExternalAuthenticationContext extContext = authContext.getSubcontext(ExternalAuthenticationContext.class);
+        if (extContext == null) {
+            throw new ExternalAuthenticationException("No ExternalInterceptorContext found");
+        }
+        
+        return extContext;
     }
     
     /**
@@ -209,11 +261,17 @@ public class ExternalAuthentication {
      * the servlet session and exposing it as request attributes.
      * 
      * @param request servlet request
+     * @param profileRequestContext current profile request context
+     * @param externalAuthenticationContext external authentication context
      * 
      * @throws ExternalAuthenticationException if an error occurs
      */
-    protected void doStart(@Nonnull final HttpServletRequest request) throws ExternalAuthenticationException {
-        throw new ExternalAuthenticationException("Not implemented");
+    protected void doStart(@Nonnull final HttpServletRequest request,
+            @Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final ExternalAuthenticationContext externalAuthenticationContext)
+                    throws ExternalAuthenticationException {
+        
+        request.setAttribute(ProfileRequestContext.BINDING_KEY, profileRequestContext);
     }
 
     /**
@@ -223,26 +281,15 @@ public class ExternalAuthentication {
      * 
      * @param request servlet request
      * @param response servlet response
+     * @param profileRequestContext current profile request context
+     * @param externalAuthenticationContext external authentication context
      * 
      * @throws ExternalAuthenticationException if an error occurs
      * @throws IOException if the redirect cannot be issued
      */
-    protected void doFinish(@Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response)
-            throws ExternalAuthenticationException, IOException {
-        throw new ExternalAuthenticationException("Not implemented");
-    }
-    
-    /**
-     * Get the {@link ProfileRequestContext} associated with a request.
-     * 
-     * @param request servlet request
-     * 
-     * @return the profile request context
-     * @throws ExternalAuthenticationException if an error occurs
-     */
-    @Nonnull protected ProfileRequestContext getProfileRequestContext(@Nonnull final HttpServletRequest request)
-            throws ExternalAuthenticationException {
-        throw new ExternalAuthenticationException("Not implemented");
-    }
+    protected abstract void doFinish(@Nonnull final HttpServletRequest request,
+            @Nonnull final HttpServletResponse response, @Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final ExternalAuthenticationContext externalAuthenticationContext)
+            throws ExternalAuthenticationException, IOException;
     
 }
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/ExternalAuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/ExternalAuthenticationContext.java
index 48a7533..bd6369e 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/ExternalAuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/ExternalAuthenticationContext.java
@@ -28,8 +28,10 @@ import javax.security.auth.Subject;
 
 import org.opensaml.messaging.context.BaseContext;
 
+import net.shibboleth.idp.authn.ExternalAuthentication;
 import net.shibboleth.utilities.java.support.annotation.constraint.Live;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
  * A context representing the state of an externalized authentication attempt,
@@ -40,6 +42,9 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElemen
  */
 public final class ExternalAuthenticationContext extends BaseContext {
     
+    /** Implementation object. */
+    @Nonnull private final ExternalAuthentication externalAuthentication; 
+    
     /** Value of flowExecutionUrl on branching from flow. */
     @Nullable private String flowExecutionUrl;
 
@@ -70,12 +75,28 @@ public final class ExternalAuthenticationContext extends BaseContext {
     /** Flag indicating this "new" result is really "old". */
     private boolean previousResult;
     
-    /** Constructor. */
-    public ExternalAuthenticationContext() {
+    /**
+     * Constructor.
+     * 
+     * @param authentication implementation object
+     */
+    public ExternalAuthenticationContext(@Nonnull final ExternalAuthentication authentication) {
+        externalAuthentication = Constraint.isNotNull(authentication, "ExternalAuthentication cannot be null");
         authenticatingAuthorities = new ArrayList<>();
     }
     
     /**
+     * Get the {@link ExternalAuthentication} object installed in the context.
+     * 
+     * @return the external authentication implementation
+     * 
+     * @since 4.0.0
+     */
+    @Nonnull public ExternalAuthentication getExternalAuthentication() {
+        return externalAuthentication;
+    }
+    
+    /**
      * Get the flow execution URL to return control to.
      * 
      * @return return location
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java
index 5d7301d..1b4c258 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java
@@ -31,8 +31,6 @@ import javax.servlet.http.HttpServletResponse;
 import org.joda.time.DateTime;
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.context.AttributeContext;
@@ -51,38 +49,24 @@ import net.shibboleth.utilities.java.support.primitive.DeprecationSupport.Object
  * of request attributes.
  */
 public class ExternalAuthenticationImpl extends ExternalAuthentication {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ExternalAuthenticationImpl.class);
     
     /** Lookup function for relying party context. */
     @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
     
-    /** State of request to pull from. */
-    @Nonnull private final ProfileRequestContext profileRequestContext;
-    
     /** Track whether we were invoked from within another login flow. */
     private final boolean extendedFlow;
 
-    /**
-     * Constructor.
-     * 
-     * @param input profile request context to expose
-     */
-    public ExternalAuthenticationImpl(@Nonnull final ProfileRequestContext input) {
-        profileRequestContext = Constraint.isNotNull(input, "ProfileRequestContext cannot be null");
-        extendedFlow = false;
-        relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+    /** Constructor. */
+    public ExternalAuthenticationImpl() {
+        this(false);
     }
 
     /**
      * Constructor.
      * 
-     * @param input profile request context to expose
      * @param extended called as extended flow from another login flow
      */
-    public ExternalAuthenticationImpl(@Nonnull final ProfileRequestContext input, final boolean extended) {
-        profileRequestContext = Constraint.isNotNull(input, "ProfileRequestContext cannot be null");
+    public ExternalAuthenticationImpl(final boolean extended) {
         extendedFlow = extended;
         relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
     }
@@ -100,7 +84,12 @@ public class ExternalAuthenticationImpl extends ExternalAuthentication {
 
     /** {@inheritDoc} */
     @Override
-    protected void doStart(@Nonnull final HttpServletRequest request) throws ExternalAuthenticationException {
+    protected void doStart(@Nonnull final HttpServletRequest request,
+            @Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final ExternalAuthenticationContext externalAuthenticationContext)
+                    throws ExternalAuthenticationException {
+        super.doStart(request, profileRequestContext, externalAuthenticationContext);
+        
         final AuthenticationContext authnContext = profileRequestContext.getSubcontext(AuthenticationContext.class);
         if (authnContext == null) {
             throw new ExternalAuthenticationException("No AuthenticationContext found");
@@ -108,7 +97,6 @@ public class ExternalAuthenticationImpl extends ExternalAuthentication {
             throw new ExternalAuthenticationException("No attempted authentication flow set");
         }
         
-        request.setAttribute(ProfileRequestContext.BINDING_KEY, profileRequestContext);
         request.setAttribute(EXTENDED_FLOW_PARAM, extendedFlow);
         
         request.setAttribute(PASSIVE_AUTHN_PARAM, authnContext.isPassive());
@@ -123,18 +111,12 @@ public class ExternalAuthenticationImpl extends ExternalAuthentication {
  // Checkstyle: CyclomaticComplexity|MethodLength OFF
     /** {@inheritDoc} */
     @Override
-    protected void doFinish(@Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response)
-            throws ExternalAuthenticationException, IOException {
-        final AuthenticationContext authnContext = profileRequestContext.getSubcontext(AuthenticationContext.class);
-        if (authnContext == null) {
-            throw new ExternalAuthenticationException("No AuthenticationContext found");
-        }
+    protected void doFinish(@Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response,
+            @Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final ExternalAuthenticationContext extContext)
+                    throws ExternalAuthenticationException, IOException {
         
-        final ExternalAuthenticationContext extContext =
-                authnContext.getSubcontext(ExternalAuthenticationContext.class);
-        if (extContext == null) {
-            throw new ExternalAuthenticationException("No ExternalAuthenticationContext found");
-        } else if (extContext.getFlowExecutionUrl() == null) {
+        if (extContext.getFlowExecutionUrl() == null) {
             throw new ExternalAuthenticationException("No flow execution URL found to return control");
         }
         
@@ -199,20 +181,12 @@ public class ExternalAuthenticationImpl extends ExternalAuthentication {
         attr = request.getAttribute(REVOKECONSENT_KEY);
         if (attr != null && attr instanceof Boolean && ((Boolean) attr).booleanValue()) {
             final ConsentManagementContext consentCtx =
-                    getProfileRequestContext(request).getSubcontext(ConsentManagementContext.class, true);
+                    profileRequestContext.getSubcontext(ConsentManagementContext.class, true);
             consentCtx.setRevokeConsent(true);
         }
         
         response.sendRedirect(extContext.getFlowExecutionUrl());
     }
 // Checkstyle: CyclomaticComplexity|MethodLength OFF
-
-    /** {@inheritDoc} */
-    @Override
-    protected ProfileRequestContext getProfileRequestContext(@Nonnull final HttpServletRequest request)
-            throws ExternalAuthenticationException {
-        return profileRequestContext;
-    }
-    
-    
+        
 }
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthenticationTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthenticationTest.java
index 3ee4c8c..8ae5cf8 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthenticationTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateExternalAuthenticationTest.java
@@ -26,6 +26,7 @@ import javax.security.auth.login.LoginException;
 import javax.servlet.http.HttpServletRequest;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.ExternalAuthentication;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.context.AuthenticationErrorContext;
 import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
@@ -43,12 +44,16 @@ import org.testng.annotations.Test;
 /** {@link ValidateExternalAuthentication} unit test. */
 public class ValidateExternalAuthenticationTest extends BaseAuthenticationContextTest {
     
+    private ExternalAuthentication ext;
+    
     private ValidateExternalAuthentication action;
     
     @BeforeMethod public void setUp() throws Exception {
         super.setUp();
         
         prc.getSubcontext(AuthenticationContext.class).setAttemptedFlow(authenticationFlows.get(0));
+        
+        ext = new ExternalAuthenticationImpl();
 
         action = new ValidateExternalAuthentication();
         action.setHttpServletRequest((HttpServletRequest) src.getExternalContext().getNativeRequest());
@@ -69,7 +74,7 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testNoCredentials() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         
         final Event event = action.execute(src);
         ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
@@ -77,7 +82,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testPrincipalName() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         eac.setPrincipalName("foo");
         
         final Event event = action.execute(src);
@@ -90,7 +96,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
     
     @Test public void testPrincipal() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         eac.setPrincipal(new TestPrincipal("foo"));
         
         final Event event = action.execute(src);
@@ -103,7 +110,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testSubject() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         final Subject subject = new Subject();
         eac.setSubject(subject);
         subject.getPrincipals().add(new TestPrincipal("foo"));
@@ -118,7 +126,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testAuthnInstant() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         eac.setPrincipalName("foo");
         final Instant ts = Instant.now().minusSeconds(3600);
         eac.setAuthnInstant(ts);
@@ -133,7 +142,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testAuthnAuthorities() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         eac.setPrincipalName("foo");
         eac.getAuthenticatingAuthorities().addAll(Arrays.asList("foo", "bar", "baz"));
         eac.setPreviousResult(true);
@@ -150,7 +160,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testException() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         eac.setAuthnException(new LoginException("foo"));
         
         final Event event = action.execute(src);
@@ -162,7 +173,8 @@ public class ValidateExternalAuthenticationTest extends BaseAuthenticationContex
 
     @Test public void testError() {
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        final ExternalAuthenticationContext eac = ac.getSubcontext(ExternalAuthenticationContext.class, true);
+        final ExternalAuthenticationContext eac =
+                (ExternalAuthenticationContext) ac.addSubcontext(new ExternalAuthenticationContext(ext), true);
         eac.setAuthnError("foo");
         
         final Event event = action.execute(src);
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnControllerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnControllerTest.java
index d77e8df..d557d12 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnControllerTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnControllerTest.java
@@ -21,8 +21,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import java.io.IOException;
-import java.security.PrivilegedActionException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -30,26 +28,34 @@ import java.util.Map;
 import javax.annotation.Nonnull;
 import javax.security.auth.Subject;
 import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.login.LoginException;
+import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.ExternalAuthentication;
 import net.shibboleth.idp.authn.ExternalAuthenticationException;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
+import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
+import net.shibboleth.idp.profile.RequestContextBuilder;
 import net.shibboleth.utilities.java.support.codec.Base64Support;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 import org.ietf.jgss.GSSContext;
 import org.ietf.jgss.GSSException;
 import org.ietf.jgss.GSSName;
 import org.mockito.ArgumentMatchers;
+import org.opensaml.messaging.context.BaseContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.http.HttpHeaders;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockServletContext;
 import org.springframework.web.servlet.ModelAndView;
+import org.springframework.webflow.execution.RequestContext;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -83,144 +89,128 @@ public class SPNEGOAuthnControllerTest {
     }
 
     @Test(expectedExceptions = {ExternalAuthenticationException.class})
-    public void withoutConversationKeyParameter_startSPNEGO_shouldThrowExternalAuthenticationException()
-            throws ExternalAuthenticationException, IOException {
-        MockHttpServletRequest req = new MockHttpServletRequest();
-        controller.startSPNEGO(TEST_CONVERSATION_KEY, req, null);
+    public void withoutConversationKeyParameter_startSPNEGO_shouldThrowExternalAuthenticationException() throws Exception {
+        controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (HttpServletRequest) buildConversationRequestContext(null).getExternalContext().getNativeRequest(), null);
     }
 
     @Test(expectedExceptions = ExternalAuthenticationException.class)
-    public void givenMismatchedKeys_startSPNEGO_shouldThrowExternalAuthenticationException()
-            throws ExternalAuthenticationException, IOException {
+    public void givenMismatchedKeys_startSPNEGO_shouldThrowExternalAuthenticationException() throws Exception {
         controller.startSPNEGO("e1s2",
-                buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, new StubExternalAuthentication()), null);
+                (HttpServletRequest) buildConversationRequestContext(TEST_CONVERSATION_KEY).getExternalContext().getNativeRequest(), null);
     }
 
     @Test(expectedExceptions = ExternalAuthenticationException.class)
-    public void givenNullKey_startSPNEGO_shouldReturnAuthenticationException() throws ExternalAuthenticationException,
-            IOException {
-        MockHttpServletRequest req = buildConversationHttpServletRequest(null, new StubExternalAuthentication());
-        controller.startSPNEGO(TEST_CONVERSATION_KEY, req, null);
+    public void givenNullKey_startSPNEGO_shouldReturnAuthenticationException() throws Exception {
+        RequestContext req = buildConversationRequestContext(null);
+        controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (MockHttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (MockHttpServletResponse) req.getExternalContext().getNativeResponse());
     }
 
     @Test
-    public void withoutSPNEGOContext_startSPNEGO_shouldReturnAuthenticationError()
-            throws ExternalAuthenticationException, IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        prc.addSubcontext(new AuthenticationContext());
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
-        ModelAndView mv = controller.startSPNEGO(TEST_CONVERSATION_KEY, req, new MockHttpServletResponse());
+    public void withoutSPNEGOContext_startSPNEGO_shouldReturnAuthenticationError() throws Exception {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
+        ModelAndView mv = controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (MockHttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (MockHttpServletResponse) req.getExternalContext().getNativeResponse());
         assertAuthenticationError(req, mv, AuthnEventIds.INVALID_AUTHN_CTX);
     }
 
     @Test
-    public void withoutKerberosSettings_startSPNEGO_shouldReturnAuthenticationError()
-            throws ExternalAuthenticationException, IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        AuthenticationContext ac = new AuthenticationContext();
+    public void withoutKerberosSettings_startSPNEGO_shouldReturnAuthenticationError() throws Exception {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
+        final ProfileRequestContext prc =
+                (ProfileRequestContext) req.getConversationScope().get(ProfileRequestContext.BINDING_KEY);
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         SPNEGOContext sc = new SPNEGOContext();
         ac.addSubcontext(sc);
-        prc.addSubcontext(ac);
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
-        ModelAndView mv = controller.startSPNEGO(TEST_CONVERSATION_KEY, req, new MockHttpServletResponse());
+        ModelAndView mv = controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (MockHttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (MockHttpServletResponse) req.getExternalContext().getNativeResponse());
         assertAuthenticationError(req, mv, AuthnEventIds.INVALID_AUTHN_CTX);
     }
 
     @Test
-    public void givenKerberosSettings_startSPNEGO_shouldReturnModelAndView() throws ExternalAuthenticationException,
-            IOException {
-        MockHttpServletRequest req = buildKerberosContextHttpServletRequest();
-        ModelAndView modelAndView = controller.startSPNEGO(TEST_CONVERSATION_KEY, req, new MockHttpServletResponse());
-        assertModelAndView(modelAndView, req);
+    public void givenKerberosSettings_startSPNEGO_shouldReturnModelAndView() throws Exception {
+        RequestContext req = buildKerberosContextRequestContext();
+        ModelAndView mv = controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (MockHttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (MockHttpServletResponse) req.getExternalContext().getNativeResponse());
+        assertModelAndView(mv, req);
     }
 
     @Test
-    public void givenKerberosSettings_startSPNEGO_shouldPreserveQueryString() throws ExternalAuthenticationException,
-            IOException {
-        MockHttpServletRequest req = buildKerberosContextHttpServletRequest();
-        req.setQueryString("dummy query string");
-        ModelAndView modelAndView = controller.startSPNEGO(TEST_CONVERSATION_KEY, req, new MockHttpServletResponse());
-        assertModelAndView(modelAndView, req);
+    public void givenKerberosSettings_startSPNEGO_shouldPreserveQueryString() throws Exception {
+        RequestContext req = buildKerberosContextRequestContext();
+        ((MockHttpServletRequest) req.getExternalContext().getNativeRequest()).setQueryString("dummy query string");
+        ModelAndView mv = controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (MockHttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (MockHttpServletResponse) req.getExternalContext().getNativeResponse());
+        assertModelAndView(mv, req);
     }
 
     @Test
-    public void givenKerberosSettings_startSPNEGO_shouldReplyUnauthorizedNegotiate()
-            throws ExternalAuthenticationException, IOException {
-        MockHttpServletRequest req = buildKerberosContextHttpServletRequest();
-        MockHttpServletResponse res = new MockHttpServletResponse();
-        controller.startSPNEGO(TEST_CONVERSATION_KEY, req, res);
-        assertResponseUnauthorizedNegotiate(res);
+    public void givenKerberosSettings_startSPNEGO_shouldReplyUnauthorizedNegotiate() throws Exception {
+        RequestContext req = buildKerberosContextRequestContext();
+        controller.startSPNEGO(TEST_CONVERSATION_KEY,
+                (MockHttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (MockHttpServletResponse) req.getExternalContext().getNativeResponse());
+        assertResponseUnauthorizedNegotiate(req);
     }
 
     @Test
-    public void withoutNegotiateToken_continueSPNEGO_shouldReturnModelAndView() throws ExternalAuthenticationException,
-            IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
+    public void withoutNegotiateToken_continueSPNEGO_shouldReturnModelAndView() throws Exception {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
         ModelAndView mv =
-                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate", req, new MockHttpServletResponse());
+                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate",
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertModelAndView(mv, req);
     }
 
     @Test
-    public void withoutNegotiateToken_continueSPNEGO_shouldPreserveQueryString()
-            throws ExternalAuthenticationException, IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
-        req.setQueryString("dummy query string");
+    public void withoutNegotiateToken_continueSPNEGO_shouldPreserveQueryString() throws Exception {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
+        ((MockHttpServletRequest) req.getExternalContext().getNativeRequest()).setQueryString("dummy query string");
         ModelAndView mv =
-                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate", req, new MockHttpServletResponse());
+                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate",
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertModelAndView(mv, req);
     }
 
     @Test
-    public void withoutNegotiateToken_continueSPNEGO_shouldReplyUnauthorizedNegotiate()
-            throws ExternalAuthenticationException, IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
-        MockHttpServletResponse res = new MockHttpServletResponse();
-        controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate", req, res);
-        assertResponseUnauthorizedNegotiate(res);
+    public void withoutNegotiateToken_continueSPNEGO_shouldReplyUnauthorizedNegotiate() throws Exception {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
+        controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate",
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse());
+        assertResponseUnauthorizedNegotiate(req);
     }
 
     @Test
-    public void withoutSPNEGOContext_continueSPNEGO_shouldReturnAuthenticationError()
-            throws ExternalAuthenticationException, IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        prc.addSubcontext(new AuthenticationContext());
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
+    public void withoutSPNEGOContext_continueSPNEGO_shouldReturnAuthenticationError() throws Exception {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
         ModelAndView mv =
-                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, null);
+                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertAuthenticationError(req, mv, AuthnEventIds.INVALID_AUTHN_CTX);
     }
 
     @Test
-    public void withoutKerberosSettings_continueSPNEGO_shouldReturnAuthenticationError()
-            throws ExternalAuthenticationException, IOException {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ProfileRequestContext prc = new ProfileRequestContext();
-        ea.setProfileRequestContext(prc);
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
+    public void withoutKerberosSettings_continueSPNEGO_shouldReturnAuthenticationError() throws Exception {
+        final RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
         ModelAndView mv =
-                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, null);
+                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertAuthenticationError(req, mv, AuthnEventIds.INVALID_AUTHN_CTX);
     }
 
     @Test
-    public void givenFailedGSSContextAcceptorInstantiation_continueSPNEGO_shouldReturnAuthenticationException()
-            throws ExternalAuthenticationException, IOException {
+    public void givenFailedGSSContextAcceptorInstantiation_continueSPNEGO_shouldReturnAuthenticationException() throws Exception {
         final GSSException expected = new GSSException(0);
         SPNEGOAuthnController failedGSSController = new SPNEGOAuthnController() {
             @Override
@@ -230,155 +220,152 @@ public class SPNEGOAuthnControllerTest {
                 throw expected;
             }
         };
-        MockHttpServletRequest req = buildKerberosContextHttpServletRequest();
+        final RequestContext req = buildKerberosContextRequestContext();
         ModelAndView mv =
-                failedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req,
-                        null);
+                failedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
 
-        Assert.assertSame(((ExternalAuthenticationException) req
+        Assert.assertSame(((Exception) ((ServletRequest) req.getExternalContext().getNativeRequest())
                 .getAttribute(ExternalAuthentication.AUTHENTICATION_EXCEPTION_KEY)).getCause(), expected);
         assertAuthenticationExceptionCause(req, mv, GSSException.class);
     }
 
     @Test
-    public void givenSuccessfulGSSContextAcceptorInstantiation_continueSPNEGO_shouldHaveSetAcceptorInSPNEGOContext()
-            throws ExternalAuthenticationException, IOException, Exception {
-        GSSContext mockGSSContext = mock(GSSContext.class);
+    public void givenSuccessfulGSSContextAcceptorInstantiation_continueSPNEGO_shouldHaveSetAcceptorInSPNEGOContext() throws Exception {
+        final GSSContext mockGSSContext = mock(GSSContext.class);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(mockGSSContext);
         when(mockGSSContext.isEstablished()).thenReturn(false);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        MockHttpServletResponse res = new MockHttpServletResponse();
-        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, res);
-
-        StubExternalAuthentication ea =
-                (StubExternalAuthentication) req.getSession(true).getAttribute(
-                        ExternalAuthentication.CONVERSATION_KEY + TEST_CONVERSATION_KEY);
-        ProfileRequestContext prc = ea.getProfileRequestContext(req);
-        AuthenticationContext authnContext = prc.getSubcontext(AuthenticationContext.class);
-        SPNEGOContext spnegoContext = authnContext != null ? authnContext.getSubcontext(SPNEGOContext.class) : null;
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse());
+
+        final AuthenticationContext authnContext =
+                ((BaseContext) req.getConversationScope().get(ProfileRequestContext.BINDING_KEY)).getSubcontext(AuthenticationContext.class);
+        final SPNEGOContext spnegoContext = authnContext != null ? authnContext.getSubcontext(SPNEGOContext.class) : null;
         Assert.assertNotNull(spnegoContext);
         Assert.assertEquals(spnegoContext.getContextAcceptor(), mockGSSContextAcceptor);
     }
 
     @Test
-    public void givenHeaderAuthorizationNegotiate_withNTLMdata_continueSPNEGO_shouldReturnAuthenticationError()
-            throws ExternalAuthenticationException, IOException {
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NTLMSSP_HEADER_DATA);
-        ModelAndView mv =
-                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NTLMSSP_HEADER_DATA, req, null);
+    public void givenHeaderAuthorizationNegotiate_withNTLMdata_continueSPNEGO_shouldReturnAuthenticationError() throws Exception {
+        final RequestContext req = buildSPNEGORequestContext(NTLMSSP_HEADER_DATA);
+        final ModelAndView mv =
+                controller.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NTLMSSP_HEADER_DATA,
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertAuthenticationError(req, mv, SPNEGOAuthnController.NTLM_UNSUPPORTED);
     }
 
     @Test
-    public void whenAcceptSecContextThrowsException_continueSPNEGO_shouldReturnAuthenticationException()
-            throws ExternalAuthenticationException, IOException, LoginException, GSSException,
-            PrivilegedActionException, Exception {
-        RuntimeException e = new RuntimeException();
+    public void whenAcceptSecContextThrowsException_continueSPNEGO_shouldReturnAuthenticationException() throws Exception {
+        final RuntimeException e = new RuntimeException();
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenThrow(e);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        ModelAndView mv =
-                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req,
-                        null);
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        final ModelAndView mv =
+                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
 
-        Assert.assertSame(((ExternalAuthenticationException) req
+        Assert.assertSame(((Exception) ((HttpServletRequest) req.getExternalContext().getNativeRequest())
                 .getAttribute(ExternalAuthentication.AUTHENTICATION_EXCEPTION_KEY)).getCause(), e);
         assertAuthenticationExceptionCause(req, mv, RuntimeException.class);
     }
 
     @Test
-    public void withoutGSSContext_continueSPNEGO_shouldReturnModelAndView() throws LoginException, GSSException,
-            PrivilegedActionException, ExternalAuthenticationException, IOException, Exception {
+    public void withoutGSSContext_continueSPNEGO_shouldReturnModelAndView() throws Exception {
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(null);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        ModelAndView modelAndView =
-                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "", req, new MockHttpServletResponse());
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        final ModelAndView modelAndView =
+                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "",
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertModelAndView(modelAndView, req);
     }
 
     @Test
-    public void withoutGSSContext_continueSPNEGO_shouldReplyUnauthorizedNegotiate() throws LoginException,
-            GSSException, PrivilegedActionException, ExternalAuthenticationException, IOException, Exception {
+    public void withoutGSSContext_continueSPNEGO_shouldReplyUnauthorizedNegotiate() throws Exception {
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(null);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        MockHttpServletResponse res = new MockHttpServletResponse();
-        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, res);
-        assertResponseUnauthorizedNegotiate(res, Base64Support.encode("tokenBytes".getBytes(), false));
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse());
+        assertResponseUnauthorizedNegotiate(req, Base64Support.encode("tokenBytes".getBytes(), false));
     }
 
     @Test
-    public void givenGSSContextNotEstablished_continueSPNEGO_shouldReturnModelAndView() throws LoginException,
-            GSSException, PrivilegedActionException, ExternalAuthenticationException, IOException, Exception {
-        GSSContext mockGSSContext = mock(GSSContext.class);
+    public void givenGSSContextNotEstablished_continueSPNEGO_shouldReturnModelAndView() throws Exception {
+        final GSSContext mockGSSContext = mock(GSSContext.class);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(mockGSSContext);
         when(mockGSSContext.isEstablished()).thenReturn(false);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        ModelAndView modelAndView =
-                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "", req, new MockHttpServletResponse());
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        final ModelAndView modelAndView =
+                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "",
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
         assertModelAndView(modelAndView, req);
     }
 
     @Test
-    public void givenGSSContextNotEstablished_continueSPNEGO_shouldReplyUnauthorizedNegotiate() throws LoginException,
-            GSSException, PrivilegedActionException, ExternalAuthenticationException, IOException, Exception {
-        GSSContext mockGSSContext = mock(GSSContext.class);
+    public void givenGSSContextNotEstablished_continueSPNEGO_shouldReplyUnauthorizedNegotiate() throws Exception {
+        final GSSContext mockGSSContext = mock(GSSContext.class);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(mockGSSContext);
         when(mockGSSContext.isEstablished()).thenReturn(false);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        MockHttpServletResponse res = new MockHttpServletResponse();
-        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, res);
-        assertResponseUnauthorizedNegotiate(res, Base64Support.encode("tokenBytes".getBytes(), false));
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse());
+        assertResponseUnauthorizedNegotiate(req, Base64Support.encode("tokenBytes".getBytes(), false));
     }
 
     @Test
-    public void givenGSSContextEstablished_andGSSException_continueSPNEGO_shouldReturnAuthenticationError()
-            throws LoginException, GSSException, PrivilegedActionException, ExternalAuthenticationException,
-            IOException, Exception {
-        GSSContext mockGSSContext = mock(GSSContext.class);
-        GSSException gssException = new GSSException(0);
+    public void givenGSSContextEstablished_andGSSException_continueSPNEGO_shouldReturnAuthenticationError() throws Exception {
+        final GSSContext mockGSSContext = mock(GSSContext.class);
+        final GSSException gssException = new GSSException(0);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(mockGSSContext);
         when(mockGSSContext.isEstablished()).thenReturn(true);
         when(mockGSSContext.getSrcName()).thenThrow(gssException);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        ModelAndView mv =
-                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req,
-                        null);
-        Assert.assertSame(((ExternalAuthenticationException) req
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        final ModelAndView mv =
+                mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                        (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                        (HttpServletResponse) req.getExternalContext().getNativeResponse());
+        Assert.assertSame(((Exception) ((HttpServletRequest) req.getExternalContext().getNativeRequest())
                 .getAttribute(ExternalAuthentication.AUTHENTICATION_EXCEPTION_KEY)).getCause(), gssException);
         assertAuthenticationExceptionCause(req, mv, GSSException.class);
     }
 
     @Test
-    public void givenGSSContextEstablished_continueSPNEGO_shouldReturnNull() throws LoginException, GSSException,
-            PrivilegedActionException, ExternalAuthenticationException, IOException, Exception {
-        GSSContext mockGSSContext = mock(GSSContext.class);
-        GSSName mockGssName = mock(GSSName.class);
+    public void givenGSSContextEstablished_continueSPNEGO_shouldReturnNull() throws Exception {
+        final GSSContext mockGSSContext = mock(GSSContext.class);
+        final GSSName mockGssName = mock(GSSName.class);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(mockGSSContext);
         when(mockGSSContext.isEstablished()).thenReturn(true);
         when(mockGSSContext.getSrcName()).thenReturn(mockGssName);
         when(mockGssName.toString()).thenReturn("testname at realm");
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        Assert.assertNull(mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate "
-                + NEGOTIATE_HEADER_DATA, req, null));
+        final RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        Assert.assertNull(mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse()));
     }
 
     @Test
-    public void givenGSSContextEstablished_continueSPNEGO_shouldSetAuthenticationSubjectAttribute()
-            throws LoginException, GSSException, PrivilegedActionException, ExternalAuthenticationException,
-            IOException, Exception {
+    public void givenGSSContextEstablished_continueSPNEGO_shouldSetAuthenticationSubjectAttribute() throws Exception {
         GSSContext mockGSSContext = mock(GSSContext.class);
         GSSName mockGssName = mock(GSSName.class);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
@@ -387,47 +374,49 @@ public class SPNEGOAuthnControllerTest {
         when(mockGSSContext.isEstablished()).thenReturn(true);
         when(mockGSSContext.getSrcName()).thenReturn(mockGssName);
         when(mockGssName.toString()).thenReturn("testname at realm");
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, null);
-        Subject s = (Subject) req.getAttribute(ExternalAuthentication.SUBJECT_KEY);
+        RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse());
+        Subject s = (Subject) ((HttpServletRequest) req.getExternalContext().getNativeRequest()).getAttribute(ExternalAuthentication.SUBJECT_KEY);
         Assert.assertEquals(s.getClass(), Subject.class);
         Assert.assertTrue(s.getPrincipals(KerberosPrincipal.class).contains(new KerberosPrincipal("testname at realm")));
         Assert.assertTrue(s.getPrincipals(UsernamePrincipal.class).contains(new UsernamePrincipal("testname at realm")));
     }
 
     @Test
-    public void givenGSSContextEstablishedButNoGSSNameIsNull_continueSPNEGO_shouldSetAuthenticationSubjectAttribute()
-            throws LoginException, GSSException, PrivilegedActionException, ExternalAuthenticationException,
-            IOException, Exception {
+    public void givenGSSContextEstablishedButNoGSSNameIsNull_continueSPNEGO_shouldSetAuthenticationSubjectAttribute() throws Exception {
         GSSContext mockGSSContext = mock(GSSContext.class);
         when(mockGSSContextAcceptor.acceptSecContext(ArgumentMatchers.<byte[]> any(), anyInt(), anyInt())).thenReturn(
                 "tokenBytes".getBytes());
         when(mockGSSContextAcceptor.getContext()).thenReturn(mockGSSContext);
         when(mockGSSContext.isEstablished()).thenReturn(true);
         when(mockGSSContext.getSrcName()).thenReturn(null);
-        MockHttpServletRequest req = buildSPNEGOHttpServletRequest(NEGOTIATE_HEADER_DATA);
-        ModelAndView mv = mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA, req, null);
+        RequestContext req = buildSPNEGORequestContext(NEGOTIATE_HEADER_DATA);
+        ModelAndView mv = mockedGSSController.continueSPNEGO(TEST_CONVERSATION_KEY, "Negotiate " + NEGOTIATE_HEADER_DATA,
+                (HttpServletRequest) req.getExternalContext().getNativeRequest(),
+                (HttpServletResponse) req.getExternalContext().getNativeResponse());
         Assert.assertNull(mv);
-        Assert.assertEquals(((ExternalAuthenticationException) req
-                .getAttribute(ExternalAuthentication.AUTHENTICATION_EXCEPTION_KEY)).getClass(),ExternalAuthenticationException.class);
+        Assert.assertEquals(
+                ((HttpServletRequest) req.getExternalContext().getNativeRequest()).getAttribute(ExternalAuthentication.AUTHENTICATION_EXCEPTION_KEY).getClass(),
+                ExternalAuthenticationException.class);
     }
 
-    private MockHttpServletRequest buildSPNEGOHttpServletRequest(String negotiateHeaderData) {
-        MockHttpServletRequest req = buildKerberosContextHttpServletRequest();
-        req.addHeader(HttpHeaders.AUTHORIZATION, "Negotiate " + negotiateHeaderData);
+    private RequestContext buildSPNEGORequestContext(String negotiateHeaderData) throws ComponentInitializationException {
+        RequestContext req = buildKerberosContextRequestContext();
+        ((MockHttpServletRequest) req.getExternalContext().getNativeRequest()).addHeader(HttpHeaders.AUTHORIZATION, "Negotiate " + negotiateHeaderData);
         return req;
     }
 
-    private MockHttpServletRequest buildKerberosContextHttpServletRequest() {
-        StubExternalAuthentication ea = new StubExternalAuthentication();
-        ea.setProfileRequestContext(buildKerberosProfileRequestContext());
-        MockHttpServletRequest req = buildConversationHttpServletRequest(TEST_CONVERSATION_KEY, ea);
+    private RequestContext buildKerberosContextRequestContext() throws ComponentInitializationException {
+        RequestContext req = buildConversationRequestContext(TEST_CONVERSATION_KEY);
+        buildKerberosProfileRequestContext(req);
         return req;
     }
 
-    private ProfileRequestContext buildKerberosProfileRequestContext() {
-        ProfileRequestContext prc = new ProfileRequestContext();
-        AuthenticationContext ac = new AuthenticationContext();
+    private ProfileRequestContext buildKerberosProfileRequestContext(RequestContext rc) {
+        ProfileRequestContext prc = (ProfileRequestContext) rc.getConversationScope().get(ProfileRequestContext.BINDING_KEY);
+        AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         SPNEGOContext sc = new SPNEGOContext();
         KerberosSettings ks = new KerberosSettings();
         List<KerberosRealmSettings> realms = new ArrayList<>();
@@ -435,42 +424,55 @@ public class SPNEGOAuthnControllerTest {
         ks.setRealms(realms);
         sc.setKerberosSettings(ks);
         ac.addSubcontext(sc);
-        prc.addSubcontext(ac);
         return prc;
     }
 
-    private MockHttpServletRequest buildConversationHttpServletRequest(String conversationKey,
-            ExternalAuthentication externalAuthentication) {
-        MockHttpServletRequest req = new MockHttpServletRequest();
-        req.addParameter(ExternalAuthentication.CONVERSATION_KEY, conversationKey);
-        req.getSession(true).setAttribute(ExternalAuthentication.CONVERSATION_KEY + TEST_CONVERSATION_KEY,
-                externalAuthentication);
-        return req;
-    }
-
-    private void assertAuthenticationError(MockHttpServletRequest request, ModelAndView mv, String expectedError) {
+    private RequestContext buildConversationRequestContext(String conversationKey) throws ComponentInitializationException {
+        final RequestContext rc = new RequestContextBuilder().buildRequestContext();
+        if (conversationKey != null) {
+            ((MockHttpServletRequest) rc.getExternalContext().getNativeRequest()).addParameter(
+                    ExternalAuthentication.CONVERSATION_KEY, conversationKey);
+        }
+        
+        final ProfileRequestContext prc =
+                (ProfileRequestContext) rc.getConversationScope().get(ProfileRequestContext.BINDING_KEY); 
+        ((MockServletContext) rc.getExternalContext().getNativeContext()).setAttribute(ExternalAuthentication.SWF_KEY, prc);
+        
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class, true);
+        ac.setAttemptedFlow(new AuthenticationFlowDescriptor());
+        
+        final ExternalAuthenticationContext eac = (ExternalAuthenticationContext) ac.addSubcontext(
+                new ExternalAuthenticationContext(new ExternalAuthenticationImpl()));
+        eac.setFlowExecutionUrl("foo");
+        
+        return rc;
+    }
+
+    private void assertAuthenticationError(RequestContext request, ModelAndView mv, String expectedError) {
         Assert.assertNull(mv);
-        Assert.assertEquals(request.getAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY).toString(),
+        Assert.assertEquals(
+                ((ServletRequest) request.getExternalContext().getNativeRequest()).getAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY).toString(),
                 expectedError);
     }
 
-    private void assertAuthenticationExceptionCause(MockHttpServletRequest request, ModelAndView mv,
+    private void assertAuthenticationExceptionCause(RequestContext request, ModelAndView mv,
             Class<?> exceptedExceptionClass) {
         Assert.assertNull(mv);
-        Assert.assertEquals(((ExternalAuthenticationException) request
+        Assert.assertEquals(((Exception) ((ServletRequest) request.getExternalContext().getNativeRequest())
                 .getAttribute(ExternalAuthentication.AUTHENTICATION_EXCEPTION_KEY)).getCause().getClass(),
                 exceptedExceptionClass);
     }
 
-    private void assertModelAndView(ModelAndView modelAndView, MockHttpServletRequest request) {
+    private void assertModelAndView(ModelAndView modelAndView, RequestContext request) {
         Assert.assertEquals(modelAndView.getViewName(), "spnego-unavailable");
         Map<String, Object> model = modelAndView.getModel();
         Assert.assertTrue(model.containsKey("encoder"), "Model doesn't contain \"encoder\"");
         Assert.assertEquals(model.get("encoder").getClass(), Class.class);
         Assert.assertTrue(model.containsKey("errorUrl"), "Model doesn't contain \"errorUrl\"");
         Assert.assertEquals(model.get("errorUrl").getClass(), String.class);
-        if (request.getQueryString() != null) {
-            Assert.assertTrue(((String) model.get("errorUrl")).endsWith("/error?" + request.getQueryString()));
+        if (((HttpServletRequest) request.getExternalContext().getNativeRequest()).getQueryString() != null) {
+            Assert.assertTrue(((String) model.get("errorUrl")).endsWith(
+                    "/error?" + ((HttpServletRequest) request.getExternalContext().getNativeRequest()).getQueryString()));
         } else {
             Assert.assertTrue(((String) model.get("errorUrl")).endsWith("/error"));
         }
@@ -478,38 +480,16 @@ public class SPNEGOAuthnControllerTest {
         Assert.assertTrue(model.get("request") instanceof HttpServletRequest);
     }
 
-    private void assertResponseUnauthorizedNegotiate(MockHttpServletResponse response) {
+    private void assertResponseUnauthorizedNegotiate(RequestContext request) {
+        final HttpServletResponse response = (HttpServletResponse) request.getExternalContext().getNativeResponse();
         Assert.assertEquals(response.getStatus(), 401);
         Assert.assertEquals(response.getHeader("WWW-Authenticate"), "Negotiate");
     }
 
-    private void assertResponseUnauthorizedNegotiate(MockHttpServletResponse response, String base64token) {
+    private void assertResponseUnauthorizedNegotiate(RequestContext request, String base64token) {
+        final HttpServletResponse response = (HttpServletResponse) request.getExternalContext().getNativeResponse();
         Assert.assertEquals(response.getStatus(), 401);
         Assert.assertEquals(response.getHeader("WWW-Authenticate"), "Negotiate " + base64token);
     }
 
-    private class StubExternalAuthentication extends ExternalAuthentication {
-
-        private ProfileRequestContext profileRequestContext;
-
-        public void setProfileRequestContext(ProfileRequestContext prc) {
-            profileRequestContext = prc;
-        }
-
-        @Override
-        protected void doStart(HttpServletRequest request) throws ExternalAuthenticationException {
-        }
-
-        @Override
-        protected void doFinish(HttpServletRequest request, HttpServletResponse response)
-                throws ExternalAuthenticationException, IOException {
-        }
-
-        @Override
-        protected ProfileRequestContext getProfileRequestContext(HttpServletRequest request)
-                throws ExternalAuthenticationException {
-            return profileRequestContext;
-        }
-
-    }
-}
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/system/flows/authn/external-authn-flow.xml b/idp-conf/src/main/resources/system/flows/authn/external-authn-flow.xml
index 3dc9b50..a0a67dc 100644
--- a/idp-conf/src/main/resources/system/flows/authn/external-authn-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/external-authn-flow.xml
@@ -7,8 +7,7 @@
     
     <view-state id="ExternalTransfer" view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.External.externalAuthnPathStrategy').apply(opensamlProfileRequestContext), flowExecutionContext.getKey().toString())}">
         <on-render>
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
-            <evaluate expression="externalContext.getNativeRequest().getSession().setAttribute('conversation' + flowExecutionContext.getKey().toString(), new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(opensamlProfileRequestContext, calledAsExtendedFlow?:false))" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(calledAsExtendedFlow?:false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
         </on-render>
         <transition to="ValidateExternalAuthentication" />
     </view-state>
diff --git a/idp-conf/src/main/resources/system/flows/authn/remoteuser-authn-flow.xml b/idp-conf/src/main/resources/system/flows/authn/remoteuser-authn-flow.xml
index c81ce16..4580a4f 100644
--- a/idp-conf/src/main/resources/system/flows/authn/remoteuser-authn-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/remoteuser-authn-flow.xml
@@ -7,10 +7,10 @@
     
     <view-state id="ExternalTransfer" view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.RemoteUser.externalAuthnPathStrategy').apply(opensamlProfileRequestContext), flowExecutionContext.getKey().toString())}">
         <on-render>
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
-            <evaluate expression="externalContext.getNativeRequest().getSession().setAttribute('conversation' + flowExecutionContext.getKey().toString(), new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(opensamlProfileRequestContext, calledAsExtendedFlow?:false))" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(calledAsExtendedFlow?:false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
         </on-render>
         <transition to="ValidateExternalAuthentication" />
+        
     </view-state>
     
     <action-state id="ValidateExternalAuthentication">
diff --git a/idp-conf/src/main/resources/system/flows/authn/spnego-authn-flow.xml b/idp-conf/src/main/resources/system/flows/authn/spnego-authn-flow.xml
index c0d8a4d..bfd5b96 100644
--- a/idp-conf/src/main/resources/system/flows/authn/spnego-authn-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/spnego-authn-flow.xml
@@ -54,8 +54,7 @@
     <view-state id="RunSPNEGO" view="externalRedirect:#{flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.SPNEGO.externalAuthnPathStrategy').apply(opensamlProfileRequestContext)}/#{flowExecutionContext.getKey().toString()}?conversation=#{flowExecutionContext.getKey().toString()}">
         <on-render>
             <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.spnego.impl.SPNEGOContext(), true).setKerberosSettings(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.SPNEGO.Krb5.Settings'))" />
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
-            <evaluate expression="externalContext.getNativeRequest().getSession().setAttribute('conversation' + flowExecutionContext.getKey().toString(), new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(opensamlProfileRequestContext))" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl()), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
         </on-render>
 
         <transition on="proceed" to="ValidateExternalAuthentication" />
diff --git a/idp-conf/src/main/resources/system/flows/authn/x509-authn-flow.xml b/idp-conf/src/main/resources/system/flows/authn/x509-authn-flow.xml
index ce1e9df..d00cd30 100644
--- a/idp-conf/src/main/resources/system/flows/authn/x509-authn-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/x509-authn-flow.xml
@@ -10,8 +10,7 @@
     
     <view-state id="ExternalTransfer" view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.X509.externalAuthnPathStrategy').apply(opensamlProfileRequestContext), flowExecutionContext.getKey().toString())}">
         <on-render>
-            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
-            <evaluate expression="externalContext.getNativeRequest().getSession().setAttribute('conversation' + flowExecutionContext.getKey().toString(), new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(opensamlProfileRequestContext))" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(calledAsExtendedFlow?:false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
         </on-render>
         <transition to="ValidateExternalAuthentication" />
     </view-state>
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ExternalInterceptor.java b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ExternalInterceptor.java
index 385e5f5..d6d4c70 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ExternalInterceptor.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/profile/interceptor/ExternalInterceptor.java
@@ -31,6 +31,7 @@ import org.springframework.webflow.execution.repository.FlowExecutionRepository;
 import org.springframework.webflow.execution.repository.FlowExecutionRepositoryException;
 import org.springframework.webflow.executor.FlowExecutorImpl;
 
+import com.google.common.base.Strings;
 import com.google.common.net.UrlEscapers;
 
 import net.shibboleth.idp.profile.context.ExternalInterceptorContext;
@@ -45,10 +46,10 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  */
 public abstract class ExternalInterceptor {
 
-    /** Parameter supplied to locate the SWF object needed in the session. */
+    /** Parameter supplied to locate the SWF object needed in the servlet context. */
     @Nonnull @NotEmpty public static final String SWF_KEY = "net.shibboleth.idp.flowExecutor";
     
-    /** Parameter supplied to identify the per-conversation structure in the session. */
+    /** Parameter supplied to identify the per-conversation parameter. */
     @Nonnull @NotEmpty public static final String CONVERSATION_KEY = "conversation";
 
     /** Request attribute to which an event ID may be bound. */
@@ -94,7 +95,7 @@ public abstract class ExternalInterceptor {
     @Nonnull @NotEmpty public static String startExternalInterceptor(@Nonnull final HttpServletRequest request)
             throws ExternalInterceptorException {
         final String key = request.getParameter(CONVERSATION_KEY);
-        if (key == null || key.isEmpty()) {
+        if (Strings.isNullOrEmpty(key)) {
             throw new ExternalInterceptorException("No conversation key found in request");
         }
         

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list