Преглед изворни кода

Committing ConcurrentSessionController feature and tests. Documentation is needed.

Ray Krueger пре 20 година
родитељ
комит
44397bb05d

+ 33 - 0
core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java

@@ -0,0 +1,33 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.providers;
+
+import net.sf.acegisecurity.AuthenticationException;
+
+
+/**
+ * Thrown by the ConcurrentSessionController when the number of sessions
+ * allowed is attempting to be exceeded.
+ *
+ * @author Ray Krueger
+ */
+public class ConcurrentLoginException extends AuthenticationException {
+    //~ Constructors ===========================================================
+
+    public ConcurrentLoginException(String msg) {
+        super(msg);
+    }
+}

+ 36 - 0
core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java

@@ -0,0 +1,36 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.providers;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+
+
+/**
+ * See: {@link ConcurrentSessionControllerImpl}
+ *
+ * @author Ray Krueger
+ * @see ConcurrentSessionControllerImpl
+ */
+public interface ConcurrentSessionController {
+    //~ Methods ================================================================
+
+    void afterAuthentication(Authentication initialAuth, Authentication result)
+            throws AuthenticationException;
+
+    void beforeAuthentication(Authentication initialAuth)
+            throws AuthenticationException;
+}

+ 290 - 0
core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java

@@ -0,0 +1,290 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.providers;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationTrustResolver;
+import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
+import net.sf.acegisecurity.UserDetails;
+import net.sf.acegisecurity.ui.WebAuthenticationDetails;
+import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
+
+import org.springframework.context.ApplicationEvent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * Used by the {@link ProviderManager} to track Authentications and their
+ * respective sessions. A given user is allowed {@link #setMaxSessions(int)}
+ * sessions. If they attempt to exceed that ammount a {@link
+ * ConcurrentLoginException} will be thrown. The
+ * ConcurrentSessionControllerImpl class will listen for {@link
+ * HttpSessionDestroyedEvent}s in the ApplicationContext to remove a session
+ * from the internal tracking. <b>This class will not function properly
+ * without a {@link net.sf.acegisecurity.ui.session.HttpSessionEventPublisher}
+ * configured in web.xml.</b>
+ *
+ * @author Ray Krueger
+ * @author Ben Alex
+ */
+public class ConcurrentSessionControllerImpl
+    implements ConcurrentSessionController {
+    //~ Instance fields ========================================================
+
+    protected Map principalsToSessions = new HashMap();
+    protected Map sessionsToPrincipals = new HashMap();
+    protected Set sessionSet = new HashSet();
+    private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+    private int maxSessions = 1;
+
+    //~ Methods ================================================================
+
+    /**
+     * Set the maximum number of sessions a user is allowed to have, defaults
+     * to 1. Setting this to anything less than 1 will allow unlimited
+     * sessions
+     *
+     * @param maxSessions
+     */
+    public void setMaxSessions(int maxSessions) {
+        this.maxSessions = maxSessions;
+    }
+
+    /**
+     * The maximum sessions per user.
+     *
+     * @return int
+     */
+    public int getMaxSessions() {
+        return maxSessions;
+    }
+
+    /**
+     * The trustResolver to use for determining Anonymous users and ignoring
+     * them. Defaults to {@link AuthenticationTrustResolverImpl}
+     *
+     * @param trustResolver
+     */
+    public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
+        this.trustResolver = trustResolver;
+    }
+
+    /**
+     * Get the configured AuthenticationTrustResolver
+     *
+     * @return The configured AuthenticationTrustResolver or {@link
+     *         AuthenticationTrustResolverImpl} by default.
+     */
+    public AuthenticationTrustResolver getTrustResolver() {
+        return trustResolver;
+    }
+
+    /**
+     * Called by the {@link ProviderManager} after receiving a response from a
+     * configured AuthenticationProvider.
+     *
+     * @param request Used to retieve the {@link WebAuthenticationDetails}
+     * @param response Used to store the sessionId for the current Principal
+     *
+     * @see #determineSessionPrincipal(net.sf.acegisecurity.Authentication)
+     */
+    public void afterAuthentication(Authentication request,
+        Authentication response) {
+        enforceConcurrentLogins(response);
+
+        if (request.getDetails() instanceof WebAuthenticationDetails) {
+            String sessionId = ((WebAuthenticationDetails) request.getDetails())
+                .getSessionId();
+            addSession(determineSessionPrincipal(response), sessionId);
+        }
+    }
+
+    /**
+     * Called by the {@link ProviderManager} before iterating the configured
+     * {@link AuthenticationProvider}s
+     *
+     * @param request The Authentication in question
+     *
+     * @throws ConcurrentLoginException if the user has already met the {@link
+     *         #setMaxSessions(int)}
+     */
+    public void beforeAuthentication(Authentication request)
+        throws ConcurrentLoginException {
+        enforceConcurrentLogins(request);
+    }
+
+    /**
+     * Checks for {@link HttpSessionDestroyedEvent}s and calls {@link
+     * #removeSession(String)} for the destoyed HttpSessions id.
+     *
+     * @param event
+     */
+    public void onApplicationEvent(ApplicationEvent event) {
+        if (event instanceof HttpSessionDestroyedEvent) {
+            String sessionId = ((HttpSession) event.getSource()).getId();
+            removeSession(sessionId);
+        }
+    }
+
+    /**
+     * Compares the sessionIds stored for the given principal to determine if
+     * the given sessionId is new or existing.
+     *
+     * @param principal The principal in question
+     * @param sessionId The new or existing sessionId
+     *
+     * @return true if it's the same as a session already in use, false if it
+     *         is a new session
+     */
+    protected boolean isActiveSession(Object principal, String sessionId) {
+        Set sessions = (Set) principalsToSessions.get(principal);
+
+        if (sessions == null) {
+            return false;
+        }
+
+        return sessions.contains(sessionId);
+    }
+
+    /**
+     * Updates internal maps with the sessionId for the given principal. Can be
+     * overridden by subclasses to provide a specialized means of principal
+     * -&gt; session tracking.
+     *
+     * @param principal
+     * @param sessionId
+     */
+    protected void addSession(Object principal, String sessionId) {
+        Set sessions = (Set) principalsToSessions.get(principal);
+
+        if (sessions == null) {
+            sessions = new HashSet();
+            principalsToSessions.put(principal, sessions);
+        }
+
+        sessions.add(sessionId);
+        sessionsToPrincipals.put(sessionId, principal);
+    }
+
+    /**
+     * Counts the number of sessions in use by the given principal
+     *
+     * @param principal The principal object
+     *
+     * @return 0 if there are no sessions, &gt; if there are any
+     */
+    protected int countSessions(Object principal) {
+        Set set = (Set) principalsToSessions.get(principal);
+
+        if (set == null) {
+            return 0;
+        }
+
+        return set.size();
+    }
+
+    /**
+     * Checks to see if the Authentication principal is of type UserDetails. If
+     * it is then the {@link net.sf.acegisecurity.UserDetails#getUsername()}
+     * is returned. Otherwise Authentication.getPrincipal().toString() is
+     * returned. Subclasses can override this method to provide a more
+     * specific implementation.
+     *
+     * @param auth The Authentication in question
+     *
+     * @return The principal to be used as the key against sessions
+     */
+    protected Object determineSessionPrincipal(Authentication auth) {
+        if (auth.getPrincipal() instanceof UserDetails) {
+            return ((UserDetails) auth.getPrincipal()).getUsername();
+        } else {
+            return auth.getPrincipal().toString();
+        }
+    }
+
+    /**
+     * Called by both the beforeAuthentication and afterAuthentication methods.
+     * Anonymous requests as determined by the configured {@link
+     * AuthenticationTrustResolver} are ignored. If the details are
+     * WebAuthenticationDetails, get the sessionId and and the principal off
+     * of the authentication using the {@link
+     * #determineSessionPrincipal(net.sf.acegisecurity.Authentication)}
+     * method.  Uses the sessionId and principal to determine if the session
+     * is new, and if the user is already at the maxSessions value. Subclasses
+     * may override for more specific functionality
+     *
+     * @param request Authentication being evaluated
+     *
+     * @throws ConcurrentLoginException If the session is new, and the user is
+     *         already at maxSessions
+     */
+    protected void enforceConcurrentLogins(Authentication request)
+        throws ConcurrentLoginException {
+        //If the max is less than 1, sessions are unlimited
+        if (maxSessions < 1) {
+            return;
+        }
+
+        //If it is an anonymous user, ignore them
+        if (trustResolver.isAnonymous(request)) {
+            return;
+        }
+
+        if (request.getDetails() instanceof WebAuthenticationDetails) {
+            String sessionId = ((WebAuthenticationDetails) request.getDetails())
+                .getSessionId();
+
+            Object principal = determineSessionPrincipal(request);
+
+            if (!isActiveSession(principal, sessionId)) {
+                if (maxSessions == countSessions(principal)) {
+                    //The user is AT their max, toss them out
+                    throw new ConcurrentLoginException(principal
+                        + " has reached the maximum concurrent logins");
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove the given sessionId from storage. Used by {@link
+     * #onApplicationEvent(org.springframework.context.ApplicationEvent)} for
+     * HttpSessionDestroyedEvent
+     *
+     * @param sessionId
+     */
+    protected void removeSession(String sessionId) {
+        // find out which principal is associated with this sessionId
+        Object associatedPrincipal = sessionsToPrincipals.get(sessionId);
+
+        if (associatedPrincipal != null) {
+            Set sessions = (Set) principalsToSessions.get(associatedPrincipal);
+            sessions.remove(sessionId);
+
+            if (sessions.isEmpty()) {
+                principalsToSessions.remove(associatedPrincipal);
+            }
+
+            sessionsToPrincipals.remove(sessionId);
+        }
+    }
+}

+ 41 - 0
core/src/main/java/org/acegisecurity/providers/NullConcurrentSessionController.java

@@ -0,0 +1,41 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.providers;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+
+
+/**
+ * Do nothing implementation of ConcurrentSessionController. This is the {@link ProviderManager} default
+ *
+ * @author Ray Krueger
+ * @see ConcurrentSessionControllerImpl
+ */
+public class NullConcurrentSessionController
+        implements ConcurrentSessionController {
+    //~ Methods ================================================================
+
+    public void afterAuthentication(Authentication initialAuth,
+                                    Authentication result) throws AuthenticationException {
+        //Do nothing
+    }
+
+    public void beforeAuthentication(Authentication initialAuth)
+            throws AuthenticationException {
+        //Do nothing
+    }
+}

+ 35 - 2
core/src/main/java/org/acegisecurity/providers/ProviderManager.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,11 +30,16 @@ import java.util.List;
 
 /**
  * Iterates an {@link Authentication} request through a list of {@link
- * AuthenticationProvider}s.
+ * AuthenticationProvider}s. Can optionally be configured with a {@link
+ * ConcurrentSessionController} to limit the number of sessions a user can
+ * have.
  *
  * @author Ben Alex
  * @author Wesley Hall
+ * @author Ray Krueger
  * @version $Id$
+ *
+ * @see ConcurrentSessionController
  */
 public class ProviderManager extends AbstractAuthenticationManager
     implements InitializingBean {
@@ -44,6 +49,7 @@ public class ProviderManager extends AbstractAuthenticationManager
 
     //~ Instance fields ========================================================
 
+    private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
     private List providers;
 
     //~ Methods ================================================================
@@ -82,6 +88,29 @@ public class ProviderManager extends AbstractAuthenticationManager
         return this.providers;
     }
 
+    /**
+     * Set the {@link ConcurrentSessionController} to be used for limiting
+     * user's sessions.  The {@link NullConcurrentSessionController} is used
+     * by default
+     *
+     * @param sessionController {@link ConcurrentSessionController}
+     */
+    public void setSessionController(
+        ConcurrentSessionController sessionController) {
+        this.sessionController = sessionController;
+    }
+
+    /**
+     * The configured {@link ConcurrentSessionController} is returned or the
+     * {@link NullConcurrentSessionController} if a specific one has not been
+     * set.
+     *
+     * @return{@link ConcurrentSessionController} instance
+     */
+    public ConcurrentSessionController getSessionController() {
+        return sessionController;
+    }
+
     public void afterPropertiesSet() throws Exception {
         checkIfValidList(this.providers);
     }
@@ -117,6 +146,8 @@ public class ProviderManager extends AbstractAuthenticationManager
 
         Class toTest = authentication.getClass();
 
+        sessionController.beforeAuthentication(authentication);
+
         while (iter.hasNext()) {
             AuthenticationProvider provider = (AuthenticationProvider) iter
                 .next();
@@ -128,6 +159,8 @@ public class ProviderManager extends AbstractAuthenticationManager
                 Authentication result = provider.authenticate(authentication);
 
                 if (result != null) {
+                    sessionController.afterAuthentication(authentication, result);
+
                     return result;
                 }
             }

+ 244 - 0
core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java

@@ -0,0 +1,244 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.providers;
+
+import junit.framework.TestCase;
+import net.sf.acegisecurity.*;
+import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
+import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.ui.WebAuthenticationDetails;
+import net.sf.acegisecurity.ui.session.HttpSessionCreatedEvent;
+import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
+
+import java.security.Principal;
+
+
+/**
+ * Tests for {@link ConcurrentSessionControllerImpl}
+ *
+ * @author Ray Krueger
+ */
+public class ConcurrentSessionControllerImplTests extends TestCase {
+    //~ Instance fields ========================================================
+
+    ConcurrentSessionControllerImpl target = new ConcurrentSessionControllerImpl();
+
+    //~ Methods ================================================================
+
+    public void testBumpCoverage() throws Exception {
+        target.onApplicationEvent(new HttpSessionCreatedEvent(new MockHttpSession()));
+    }
+
+    public void testEnforcementKnownGood() throws Exception {
+        Authentication auth = createAuthentication("user", "password", "session");
+        target.beforeAuthentication(auth);
+        target.afterAuthentication(auth, auth);
+    }
+
+    public void testEnforcementMultipleSessions() throws Exception {
+        target.setMaxSessions(5);
+
+        Authentication auth = null;
+
+        for (int i = 0; i < 5; i++) {
+            auth = createAuthentication("user", "password", String.valueOf(i));
+            target.beforeAuthentication(auth);
+            target.afterAuthentication(auth, auth);
+        }
+
+        try {
+            auth = createAuthentication("user", "password", "lastsession");
+            target.beforeAuthentication(auth);
+            fail("Only allowed 5 sessions, this should have thrown a ConcurrentLoginException");
+        } catch (ConcurrentLoginException e) {
+            assertTrue(e.getMessage().startsWith(auth.getPrincipal().toString()));
+        }
+    }
+
+    public void testEnforcementSingleSession() throws Exception {
+        target.setMaxSessions(1);
+
+        Authentication auth = createAuthentication("user", "password",
+                "session1");
+
+        target.beforeAuthentication(auth);
+        target.afterAuthentication(auth, auth);
+
+        try {
+            target.beforeAuthentication(createAuthentication("user",
+                    "password", "session2"));
+            fail("Only allowed 1 session, this should have thrown a ConcurrentLoginException");
+        } catch (ConcurrentLoginException e) {
+        }
+    }
+
+    public void testEnforcementUnlimitedSameSession() throws Exception {
+        target.setMaxSessions(1);
+
+        for (int i = 0; i < 100; i++) {
+            Authentication auth = createAuthentication("user", "password",
+                    "samesession");
+            target.beforeAuthentication(auth);
+            target.afterAuthentication(auth, auth);
+        }
+    }
+
+    public void testEnforcementUnlimitedSessions() throws Exception {
+        target.setMaxSessions(0);
+
+        for (int i = 0; i < 100; i++) {
+            Authentication auth = createAuthentication("user", "password",
+                    String.valueOf(i));
+            target.beforeAuthentication(auth);
+            target.afterAuthentication(auth, auth);
+        }
+    }
+
+    public void testEventHandler() throws Exception {
+        target.setMaxSessions(1);
+
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("user",
+                "password");
+        MockHttpSession session = new MockHttpSession();
+        MockHttpServletRequest request = new MockHttpServletRequest(auth,
+                session);
+        auth.setDetails(new WebAuthenticationDetails(request));
+
+        target.beforeAuthentication(auth);
+        target.afterAuthentication(auth, auth);
+
+        target.onApplicationEvent(new HttpSessionDestroyedEvent(session));
+
+        Authentication different = createAuthentication("user", "password",
+                "differentsession");
+        target.beforeAuthentication(different);
+        target.afterAuthentication(different, different);
+    }
+
+    public void testNonWebDetails() throws Exception {
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("asdf",
+                "asdf");
+        auth.setDetails("Hi there");
+        target.beforeAuthentication(auth);
+        target.afterAuthentication(auth, auth);
+    }
+
+    public void testPrincipals() throws Exception {
+        target.setMaxSessions(1);
+
+        final UserDetails user = new User("user", "password", true, true, true,
+                new GrantedAuthority[0]);
+        final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
+                "password", user.getAuthorities());
+        auth.setDetails(createWebDetails(auth, "session1"));
+
+        target.beforeAuthentication(auth);
+        target.afterAuthentication(auth, auth);
+
+        try {
+            UsernamePasswordAuthenticationToken otherAuth = new UsernamePasswordAuthenticationToken(new Principal() {
+                public String getName() {
+                    return "user";
+                }
+
+                public String toString() {
+                    return getName();
+                }
+            }, "password");
+
+            otherAuth.setDetails(createWebDetails(otherAuth, "session2"));
+            target.beforeAuthentication(otherAuth);
+            fail("Same principal, different principal type, different session should have thrown ConcurrentLoginException");
+        } catch (ConcurrentLoginException e) {
+        }
+    }
+
+    public void testSetMax() throws Exception {
+        target.setMaxSessions(1);
+        assertEquals(1, target.getMaxSessions());
+
+        target.setMaxSessions(2);
+        assertEquals(2, target.getMaxSessions());
+    }
+
+    public void testSetTrustManager() throws Exception {
+        assertNotNull("There is supposed to be a default AuthenticationTrustResolver",
+                target.getTrustResolver());
+
+        AuthenticationTrustResolverImpl impl = new AuthenticationTrustResolverImpl();
+        target.setTrustResolver(impl);
+        assertEquals(impl, target.getTrustResolver());
+    }
+
+    public void testUtilityMethods() throws Exception {
+        Object key = new Object();
+
+        target.addSession(key, "1");
+        target.addSession(key, "2");
+        target.addSession(key, "3");
+
+        target.removeSession("2");
+
+        assertFalse(target.isActiveSession(key, "2"));
+        assertTrue(target.isActiveSession(key, "1"));
+        assertTrue(target.isActiveSession(key, "3"));
+
+        assertNull(target.sessionsToPrincipals.get("2"));
+
+        assertEquals(2, target.countSessions(key));
+        target.addSession(key, "2");
+        assertEquals(3, target.countSessions(key));
+
+        target.addSession(key, "2");
+        target.addSession(key, "2");
+        assertEquals(3, target.countSessions(key));
+
+        assertTrue(target.isActiveSession(key, "1"));
+        assertTrue(target.isActiveSession(key, "2"));
+        assertTrue(target.isActiveSession(key, "3"));
+
+        assertFalse(target.isActiveSession(key, "nope"));
+
+        assertFalse(target.isActiveSession(new Object(), "1"));
+        assertFalse(target.isActiveSession(new Object(), "1"));
+
+        target.removeSession("nothing to see here");
+    }
+
+    private Authentication createAuthentication(String user, String password,
+                                                String sessionId) {
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
+                password);
+        auth.setDetails(createWebDetails(auth, sessionId));
+
+        return auth;
+    }
+
+    private WebAuthenticationDetails createWebDetails(Authentication auth,
+                                                      String sessionId) {
+        MockHttpSession session = new MockHttpSession(sessionId);
+        MockHttpServletRequest request = new MockHttpServletRequest(auth,
+                session);
+
+        return new WebAuthenticationDetails(request);
+    }
+
+    public void testAnonymous() throws Exception {
+        AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken("blah", "anon", new GrantedAuthority[]{new GrantedAuthorityImpl("ROLE_ANON")});
+        target.beforeAuthentication(auth);
+        target.afterAuthentication(auth, auth);
+    }
+}

+ 15 - 6
core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java

@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,11 +17,7 @@ package net.sf.acegisecurity.providers;
 
 import junit.framework.TestCase;
 
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.AuthenticationException;
-import net.sf.acegisecurity.AuthenticationServiceException;
-import net.sf.acegisecurity.GrantedAuthority;
-import net.sf.acegisecurity.GrantedAuthorityImpl;
+import net.sf.acegisecurity.*;
 
 import java.util.List;
 import java.util.Vector;
@@ -110,6 +106,19 @@ public class ProviderManagerTests extends TestCase {
         assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
     }
 
+    public void testConcurrentSessionControllerConfiguration()
+        throws Exception {
+        ProviderManager target = new ProviderManager();
+
+        //The NullConcurrentSessionController should be the default
+        assertNotNull(target.getSessionController());
+        assertTrue(target.getSessionController() instanceof NullConcurrentSessionController);
+
+        ConcurrentSessionControllerImpl impl = new ConcurrentSessionControllerImpl();
+        target.setSessionController(impl);
+        assertEquals(impl, target.getSessionController());
+    }
+
     public void testStartupFailsIfProviderListDoesNotContainingProviders()
         throws Exception {
         List providers = new Vector();

+ 5 - 1
doc/xdocs/upgrade/upgrade-070-080.html

@@ -26,7 +26,11 @@ applications:
 	SecureContext (and its implementation). These classes were moved as part of refactorings aimed at
 	improving the simplicity of the project's design.<br><br></li>
 
-    <li>The JaasAuthenticationCallbackHandler interface has had it's setAuthentication method removed. The handle method now takes both the Callback and Authentication objects as arguments.<br><br></li>
+	<li>If you wish to use the new ConcurrentSessionController you must declare the HttpSessionEventPublisher context listener in your
+	web.xml<br><br></li>
+
+    <li>The JaasAuthenticationCallbackHandler interface has had it's setAuthentication method removed.
+    The handle method now takes both the Callback and Authentication objects as arguments.<br><br></li>
     
     <li>Added AuthenticationException to the AutenticationEntryPoint.commence method signature.<br><br></li>
     

+ 5 - 0
samples/contacts/src/main/webapp/filter/WEB-INF/web.xml

@@ -58,6 +58,11 @@
 		<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
 	</listener>
 
+    <!--
+        The HttpSessionEventPublisher will publish
+        HttpSessionCreatedEvent and HttpSessionDestroyedEvent
+        to the WebApplicationContext
+    -->
     <listener>
         <listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
     </listener>