SEC-1229: Removed legacy concurrency classes

This commit is contained in:
Luke Taylor 2009-09-29 16:18:25 +00:00
parent 7247902911
commit 2a1430f1ce
9 changed files with 10 additions and 496 deletions

View File

@ -1,14 +1,10 @@
package org.springframework.security.config.authentication;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.concurrent.ConcurrentSessionControllerImpl;
import org.springframework.security.authentication.concurrent.SessionRegistryImpl;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
/**
@ -19,13 +15,6 @@ import org.springframework.security.config.util.InMemoryXmlApplicationContext;
public class AuthenticationManagerBeanDefinitionParserTests {
private AbstractXmlApplicationContext appContext;
private final String SESSION_CONTROLLER =
"<b:bean id='sc' class='" + ConcurrentSessionControllerImpl.class.getName() + "'>" +
" <b:property name='sessionRegistry'>" +
" <b:bean class='" + SessionRegistryImpl.class.getName() + "'/>" +
" </b:property>" +
"</b:bean>";
@Test
// SEC-1225
public void providersAreRegisteredAsTopLevelBeans() throws Exception {
@ -36,23 +25,10 @@ public class AuthenticationManagerBeanDefinitionParserTests {
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
" </user-service>" +
" </authentication-provider>" +
"</authentication-manager>" + SESSION_CONTROLLER, "3.0");
"</authentication-manager>", "3.0");
assertEquals(1, appContext.getBeansOfType(AuthenticationProvider.class).size());
}
@Test(expected=XmlBeanDefinitionStoreException.class)
public void sessionControllerRefAttributeIsRejectedFor30Context() throws Exception {
setContext(
"<authentication-manager session-controller-ref='sc'>" +
" <authentication-provider>" +
" <user-service>" +
" <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
" </user-service>" +
" </authentication-provider>" +
"</authentication-manager>" + SESSION_CONTROLLER, "3.0");
appContext.getBean(BeanIds.AUTHENTICATION_MANAGER);
}
private void setContext(String context, String version) {
appContext = new InMemoryXmlApplicationContext(context, version, null);
}

View File

@ -8,10 +8,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent;
import org.springframework.security.authentication.event.AuthenticationFailureConcurrentLoginEvent;
import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent;
import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent;
import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent;
@ -70,8 +68,6 @@ public class DefaultAuthenticationEventPublisher implements AuthenticationEventP
AuthenticationFailureBadCredentialsEvent.class.getName());
exceptionMappings.put(UsernameNotFoundException.class.getName(),
AuthenticationFailureBadCredentialsEvent.class.getName());
exceptionMappings.put(ConcurrentLoginException.class.getName(),
AuthenticationFailureConcurrentLoginEvent.class.getName());
exceptionMappings.put(ProviderNotFoundException.class.getName(),
AuthenticationFailureProviderNotFoundEvent.class.getName());
exceptionMappings.put("org.springframework.security.authentication.cas.ProxyUntrustedException",

View File

@ -24,8 +24,6 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.concurrent.ConcurrentSessionController;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
@ -35,8 +33,6 @@ import org.springframework.util.Assert;
/**
* Iterates an {@link Authentication} request through a list of {@link AuthenticationProvider}s.
*
* Can optionally be configured with a {@link ConcurrentSessionController} to limit the number of sessions a user can
* have.
* <p>
* <tt>AuthenticationProvider</tt>s are usually tried in order until one provides a non-null response.
* A non-null response indicates the provider had authority to decide on the authentication request and no further
@ -47,9 +43,8 @@ import org.springframework.util.Assert;
* If no provider returns a non-null response, or indicates it can even process an <code>Authentication</code>,
* the <code>ProviderManager</code> will throw a <code>ProviderNotFoundException</code>.
* <p>
* The exception to this process is when a provider throws an {@link AccountStatusException} or if the configured
* concurrent session controller throws a {@link ConcurrentLoginException}. In both these cases, no further providers
* in the list will be queried.
* The exception to this process is when a provider throws an {@link AccountStatusException}, in which case no
* further providers in the list will be queried.
*
* <h2>Event Publishing</h2>
* <p>
@ -68,7 +63,7 @@ import org.springframework.util.Assert;
* @author Ben Alex
* @author Luke Taylor
* @version $Id$
* @see ConcurrentSessionController
*
* @see DefaultAuthenticationEventPublisher
*/
public class ProviderManager extends AbstractAuthenticationManager implements MessageSourceAware, InitializingBean {
@ -79,7 +74,6 @@ public class ProviderManager extends AbstractAuthenticationManager implements Me
//~ Instance fields ================================================================================================
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private AuthenticationManager parent;
@ -151,20 +145,12 @@ public class ProviderManager extends AbstractAuthenticationManager implements Me
}
}
// Finally check if the concurrent session controller will allow authentication
try {
if (result != null) {
sessionController.checkAuthenticationAllowed(result);
sessionController.registerSuccessfulAuthentication(result);
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
} catch (AuthenticationException e) {
lastException = e;
if (result != null) {
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
// Session control failed, parent was null, or didn't authenticate (or throw an exception).
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",
@ -199,16 +185,6 @@ public class ProviderManager extends AbstractAuthenticationManager implements Me
return providers;
}
/**
* The configured {@link ConcurrentSessionController} is returned or the {@link
* NullConcurrentSessionController} if a specific one has not been set.
*
* @return {@link ConcurrentSessionController} instance
*/
ConcurrentSessionController getSessionController() {
return sessionController;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
@ -239,22 +215,8 @@ public class ProviderManager extends AbstractAuthenticationManager implements Me
this.providers = providers;
}
/**
* Set the {@link ConcurrentSessionController} to be used for limiting users' sessions.
*
* @param sessionController {@link ConcurrentSessionController}
*/
public void setSessionController(ConcurrentSessionController sessionController) {
this.sessionController = sessionController;
}
private static final class NullEventPublisher implements AuthenticationEventPublisher {
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {}
public void publishAuthenticationSuccess(Authentication authentication) {}
}
private static final class NullConcurrentSessionController implements ConcurrentSessionController {
public void checkAuthenticationAllowed(Authentication request) {}
public void registerSuccessfulAuthentication(Authentication authentication) {}
}
}

View File

@ -1,34 +0,0 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.authentication.concurrent;
import org.springframework.security.core.AuthenticationException;
/**
* Thrown by <code>ConcurrentSessionControllerImpl</code> if an attempt is made to login and the user has already
* exceeded their maxmimum allowed sessions.
*
* @author Ben Alex
* @version $Id$
*/
public class ConcurrentLoginException extends AuthenticationException {
//~ Constructors ===================================================================================================
public ConcurrentLoginException(String msg) {
super(msg);
}
}

View File

@ -1,174 +0,0 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.authentication.concurrent;
import java.util.List;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.util.Assert;
/**
* Base implementation of {@link ConcurrentSessionControllerImpl} which prohibits simultaneous logins.
*
* @author Ben Alex
* @version $Id$
*/
public class ConcurrentSessionControllerImpl implements ConcurrentSessionController, InitializingBean,
MessageSourceAware {
//~ Instance fields ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private SessionRegistry sessionRegistry;
private boolean exceptionIfMaximumExceeded = false;
private int maximumSessions = 1;
//~ Methods ========================================================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(sessionRegistry, "SessionRegistry required");
Assert.isTrue(maximumSessions != 0,
"MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
Assert.notNull(this.messages, "A message source must be set");
}
public void checkAuthenticationAllowed(Authentication request) throws AuthenticationException {
Assert.notNull(request, "Authentication request cannot be null (violation of interface contract)");
String sessionId = obtainSessionId(request);
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(request.getPrincipal(), false);
int sessionCount = sessions == null ? 0 : sessions.size();
int allowableSessions = getMaximumSessionsForThisUser(request);
Assert.isTrue(allowableSessions != 0, "getMaximumSessionsForThisUser() must return either -1 to allow "
+ "unlimited logins, or a positive integer to specify a maximum");
if (sessionCount < allowableSessions) {
// They haven't got too many login sessions running at present
return;
}
if (allowableSessions == -1) {
// We permit unlimited logins
return;
}
if (sessionCount == allowableSessions) {
// Only permit it though if this request is associated with one of the sessions
for (SessionInformation si : sessions) {
if (si.getSessionId().equals(sessionId)) {
return;
}
}
}
allowableSessionsExceeded(sessionId, sessions, allowableSessions, sessionRegistry);
}
/**
* Allows subclasses to customise behaviour when too many sessions are detected.
*
* @param sessionId the session ID of the present request
* @param sessions either <code>null</code> or all unexpired sessions associated with the principal
* @param allowableSessions the number of concurrent sessions the user is allowed to have
* @param registry an instance of the <code>SessionRegistry</code> for subclass use
*
* @throws ConcurrentLoginException if the
*/
protected void allowableSessionsExceeded(String sessionId, List<SessionInformation> sessions, int allowableSessions,
SessionRegistry registry) {
if (exceptionIfMaximumExceeded || (sessions == null)) {
throw new ConcurrentLoginException(messages.getMessage("ConcurrentSessionControllerImpl.exceededAllowed",
new Object[] {new Integer(allowableSessions)},
"Maximum sessions of {0} for this principal exceeded"));
}
// Determine least recently used session, and mark it for invalidation
SessionInformation leastRecentlyUsed = null;
for (int i = 0; i < sessions.size(); i++) {
if ((leastRecentlyUsed == null)
|| sessions.get(i).getLastRequest().before(leastRecentlyUsed.getLastRequest())) {
leastRecentlyUsed = sessions.get(i);
}
}
leastRecentlyUsed.expireNow();
}
public void registerSuccessfulAuthentication(Authentication authentication) {
Assert.notNull(authentication, "Authentication cannot be null (violation of interface contract)");
sessionRegistry.registerNewSession(obtainSessionId(authentication), authentication.getPrincipal());
}
/**
* Method intended for use by subclasses to override the maximum number of sessions that are permitted for
* a particular authentication. The default implementation simply returns the <code>maximumSessions</code> value
* for the bean.
*
* @param authentication to determine the maximum sessions for
*
* @return either -1 meaning unlimited, or a positive integer to limit (never zero)
*/
protected int getMaximumSessionsForThisUser(Authentication authentication) {
return maximumSessions;
}
public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
}
public void setMaximumSessions(int maximumSessions) {
this.maximumSessions = maximumSessions;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setSessionRegistry(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
public SessionRegistry getSessionRegistry() {
return sessionRegistry;
}
private String obtainSessionId(Authentication auth) {
if (auth.getDetails() == null || !(auth.getDetails() instanceof SessionIdentifierAware)) {
throw new IllegalArgumentException("The 'details' property of the supplied Authentication " +
"object must be set and must implement 'SessionIdentifierAware', but Authentication.getDetails() " +
"returned " + auth.getDetails());
}
String sessionId = ((SessionIdentifierAware) auth.getDetails()).getSessionId();
Assert.hasText(sessionId, "SessionIdentifierAware did not return a Session ID (" + auth.getDetails() + ")");
return sessionId;
}
}

View File

@ -1,35 +0,0 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.authentication.concurrent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* No-op implementation of {@link org.springframework.security.authentication.concurrent.ConcurrentSessionController}.
*
* @author Ben Alex
* @version $Id$
*/
class NullConcurrentSessionController implements ConcurrentSessionController {
//~ Methods ========================================================================================================
public void checkAuthenticationAllowed(Authentication request)
throws AuthenticationException {}
public void registerSuccessfulAuthentication(Authentication authentication) {}
}

View File

@ -1,35 +0,0 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.authentication.event;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* Application event which indicates authentication failure due to the user attempting to login to too many
* concurrent sessions.
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationFailureConcurrentLoginEvent extends AbstractAuthenticationFailureEvent {
//~ Constructors ===================================================================================================
public AuthenticationFailureConcurrentLoginEvent(Authentication authentication, AuthenticationException exception) {
super(authentication, exception);
}
}

View File

@ -16,6 +16,7 @@
package org.springframework.security.authentication;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import java.util.ArrayList;
@ -24,13 +25,6 @@ import java.util.List;
import org.junit.Test;
import org.springframework.context.MessageSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.concurrent.ConcurrentSessionController;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
@ -81,18 +75,6 @@ public class ProviderManagerTests {
verify(publisher).publishAuthenticationSuccess(result);
}
@Test
public void concurrentSessionControllerConfiguration() throws Exception {
ProviderManager target = new ProviderManager();
//The NullConcurrentSessionController should be the default
assertNotNull(target.getSessionController());
ConcurrentSessionController csc = mock(ConcurrentSessionController.class);
target.setSessionController(csc);
assertSame(csc, target.getSessionController());
}
@Test(expected=IllegalArgumentException.class)
public void startupFailsIfProviderListDoesNotContainProviders() throws Exception {
List<Object> providers = new ArrayList<Object>();
@ -193,18 +175,6 @@ public class ProviderManagerTests {
verifyZeroInteractions(otherProvider);
}
@Test(expected=ConcurrentLoginException.class)
public void concurrentLoginExceptionPreventsCallsToSubsequentProviders() throws Exception {
ProviderManager authMgr = makeProviderManager();
// Two providers so if the second is polled it will throw an BadCredentialsException
authMgr.setProviders(Arrays.asList(new MockProvider(), createProviderWhichThrows(new BadCredentialsException(""))) );
TestingAuthenticationToken request = createAuthenticationToken();
ConcurrentSessionController ctrlr = mock(ConcurrentSessionController.class);
doThrow(new ConcurrentLoginException("mocked")).when(ctrlr).checkAuthenticationAllowed(request);
authMgr.setSessionController(ctrlr);
authMgr.authenticate(request);
}
@Test
public void parentAuthenticationIsUsedIfProvidersDontAuthenticate() throws Exception {

View File

@ -1,112 +0,0 @@
/* Copyright 2004, 2005, 2006 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 org.springframework.security.authentication.concurrent;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
import org.springframework.security.authentication.concurrent.ConcurrentSessionControllerImpl;
import org.springframework.security.authentication.concurrent.SessionIdentifierAware;
import org.springframework.security.authentication.concurrent.SessionRegistry;
import org.springframework.security.authentication.concurrent.SessionRegistryImpl;
import org.springframework.security.core.Authentication;
/**
* Tests {@link ConcurrentSessionControllerImpl}.
*
* @author Ben Alex
* @version $Id$
*/
public class ConcurrentSessionControllerImplTests {
//~ Methods ========================================================================================================
private static int nextSessionId = 1000;
private Authentication createAuthentication(String user, String password) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, password);
auth.setDetails(new SessionIdentifierAware() {
private final String id = Integer.toString(nextSessionId++);
public String getSessionId() {
return id;
}
});
return auth;
}
@Test
public void testLifecycle() throws Exception {
// Build a test fixture
ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
SessionRegistry registry = new SessionRegistryImpl();
sc.setSessionRegistry(registry);
// Attempt to authenticate - it should be successful
Authentication auth = createAuthentication("bob", "1212");
sc.checkAuthenticationAllowed(auth);
sc.registerSuccessfulAuthentication(auth);
String sessionId1 = ((SessionIdentifierAware) auth.getDetails()).getSessionId();
assertFalse(registry.getSessionInformation(sessionId1).isExpired());
// Attempt to authenticate again - it should still be successful
sc.checkAuthenticationAllowed(auth);
sc.registerSuccessfulAuthentication(auth);
// Attempt to authenticate with a different session for same principal - should fail
sc.setExceptionIfMaximumExceeded(true);
Authentication auth2 = createAuthentication("bob", "1212");
assertFalse(registry.getSessionInformation(sessionId1).isExpired());
try {
sc.checkAuthenticationAllowed(auth2);
fail("Should have thrown ConcurrentLoginException");
} catch (ConcurrentLoginException expected) {
assertTrue(true);
}
// Attempt to authenticate with a different session for same principal - should expire first session
sc.setExceptionIfMaximumExceeded(false);
Authentication auth3 = createAuthentication("bob", "1212");
sc.checkAuthenticationAllowed(auth3);
sc.registerSuccessfulAuthentication(auth3);
String sessionId3 = ((SessionIdentifierAware) auth3.getDetails()).getSessionId();
assertTrue(registry.getSessionInformation(sessionId1).isExpired());
assertFalse(registry.getSessionInformation(sessionId3).isExpired());
}
@Test(expected=IllegalArgumentException.class)
public void startupDetectsInvalidMaximumSessions() throws Exception {
ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
sc.setMaximumSessions(0);
sc.afterPropertiesSet();
}
@Test(expected=IllegalArgumentException.class)
public void startupDetectsInvalidSessionRegistry() throws Exception {
ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
sc.setSessionRegistry(null);
sc.afterPropertiesSet();
}
}