SEC-710: Refactor concurrent session handling support.
This commit is contained in:
parent
bad67782a8
commit
1ae07779a2
|
@ -12,21 +12,20 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.sf.acegisecurity.concurrent;
|
||||||
package net.sf.acegisecurity.providers;
|
|
||||||
|
|
||||||
import net.sf.acegisecurity.AuthenticationException;
|
import net.sf.acegisecurity.AuthenticationException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown by the ConcurrentSessionController when the number of sessions
|
* Thrown by <code>ConcurrentSessionControllerImpl</code> if
|
||||||
* allowed is attempting to be exceeded.
|
* an attempt is made to login and the user has already exceeded
|
||||||
|
* their maxmimum allowed sessions.
|
||||||
*
|
*
|
||||||
* @author Ray Krueger
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public class ConcurrentLoginException extends AuthenticationException {
|
public class ConcurrentLoginException extends AuthenticationException {
|
||||||
//~ Constructors ===========================================================
|
|
||||||
|
|
||||||
public ConcurrentLoginException(String msg) {
|
public ConcurrentLoginException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.Authentication;
|
||||||
|
import net.sf.acegisecurity.AuthenticationException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides two methods that can be called by an {@link
|
||||||
|
* net.sf.acegisecurity.AuthenticationManager} to integrate with the
|
||||||
|
* concurrent session handling infrastructure.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public interface ConcurrentSessionController {
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by any class that wishes to know whether the current
|
||||||
|
* authentication request should be permitted. Generally callers will be
|
||||||
|
* <code>AuthenticationManager</code>s before they authenticate, but could
|
||||||
|
* equally include <code>Filter</code>s or other interceptors that wish to
|
||||||
|
* confirm the ongoing validity of a previously authenticated
|
||||||
|
* <code>Authentication</code>.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The implementation should throw a suitable exception if the user has
|
||||||
|
* exceeded their maximum allowed concurrent sessions.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param request the authentication request (never <code>null</code>)
|
||||||
|
*
|
||||||
|
* @throws AuthenticationException if the user has exceeded their maximum
|
||||||
|
* allowed current sessions
|
||||||
|
*/
|
||||||
|
public void checkAuthenticationAllowed(Authentication request)
|
||||||
|
throws AuthenticationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by an <code>AuthenticationManager</code> when the authentication
|
||||||
|
* was successful. An implementation is expected to register the
|
||||||
|
* authenticated user in some sort of registry, for future concurrent
|
||||||
|
* tracking via the {@link #checkConcurrentAuthentication(Authentication)}
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* @param authentication the successfully authenticated user (never
|
||||||
|
* <code>null</code>)
|
||||||
|
*/
|
||||||
|
public void registerSuccessfulAuthentication(Authentication authentication);
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.Authentication;
|
||||||
|
import net.sf.acegisecurity.AuthenticationException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base implementation of {@link ConcurrentSessionControllerImpl} which
|
||||||
|
* prohibits simultaneous logins.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* By default uses {@link net.sf.acegisecurity.concurrent.SessionRegistryImpl},
|
||||||
|
* although any <code>SessionRegistry</code> may be used.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class ConcurrentSessionControllerImpl
|
||||||
|
implements ConcurrentSessionController, InitializingBean {
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private SessionRegistry sessionRegistry = new SessionRegistryImpl();
|
||||||
|
private int maximumSessions = 1;
|
||||||
|
private boolean exceptionIfMaximumExceeded = false;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public void setMaximumSessions(int maximumSessions) {
|
||||||
|
this.maximumSessions = maximumSessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionRegistry(SessionRegistry sessionRegistry) {
|
||||||
|
this.sessionRegistry = sessionRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
|
||||||
|
this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkAuthenticationAllowed(Authentication request)
|
||||||
|
throws AuthenticationException {
|
||||||
|
Assert.notNull(request,
|
||||||
|
"Authentication request cannot be null (violation of interface contract)");
|
||||||
|
|
||||||
|
Object principal = SessionRegistryUtils
|
||||||
|
.obtainPrincipalFromAuthentication(request);
|
||||||
|
String sessionId = SessionRegistryUtils
|
||||||
|
.obtainSessionIdFromAuthentication(request);
|
||||||
|
|
||||||
|
SessionInformation[] sessions = sessionRegistry.getAllSessions(principal);
|
||||||
|
|
||||||
|
int sessionCount = 0;
|
||||||
|
|
||||||
|
if (sessions != null) {
|
||||||
|
sessionCount = sessions.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
} else if (sessionCount == allowableSessions) {
|
||||||
|
// Only permit it though if this request is associated with one of the sessions
|
||||||
|
for (int i = 0; i < sessionCount; i++) {
|
||||||
|
if (sessions[i].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 registry an instance of the <code>SessionRegistry</code> for subclass use
|
||||||
|
*/
|
||||||
|
protected void allowableSessionsExceeded(String sessionId, SessionInformation[] sessions, int allowableSessions, SessionRegistry registry) {
|
||||||
|
if (exceptionIfMaximumExceeded || sessions == null) {
|
||||||
|
throw new ConcurrentLoginException("Maximum sessions of "
|
||||||
|
+ allowableSessions + " for this principal exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine least recently used session, and mark it for invalidation
|
||||||
|
SessionInformation leastRecentlyUsed = null;
|
||||||
|
for (int i = 0; i < sessions.length; i++) {
|
||||||
|
if (leastRecentlyUsed == null || sessions[i].getLastRequest().before(leastRecentlyUsed.getLastRequest())) {
|
||||||
|
leastRecentlyUsed = sessions[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leastRecentlyUsed.expireNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerSuccessfulAuthentication(Authentication authentication) {
|
||||||
|
Assert.notNull(authentication,
|
||||||
|
"Authentication cannot be null (violation of interface contract)");
|
||||||
|
|
||||||
|
Object principal = SessionRegistryUtils
|
||||||
|
.obtainPrincipalFromAuthentication(authentication);
|
||||||
|
String sessionId = SessionRegistryUtils
|
||||||
|
.obtainSessionIdFromAuthentication(authentication);
|
||||||
|
|
||||||
|
sessionRegistry.removeSessionInformation(sessionId);
|
||||||
|
sessionRegistry.registerNewSession(sessionId, principal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter required by concurrent session handling package.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This filter performs two functions. First, it calls {@link
|
||||||
|
* net.sf.acegisecurity.concurrent.SessionRegistry#refreshLastRequest(String)}
|
||||||
|
* for each request. That way, registered sessions always have a correct "last
|
||||||
|
* update" date/time. Second, it retrieves {@link
|
||||||
|
* net.sf.acegisecurity.concurrent.SessionInformation} from the
|
||||||
|
* <code>SessionRegistry</code> for each request and checks if the session has
|
||||||
|
* been marked as expired. If it has been marked as expired, the session is
|
||||||
|
* invalidated. The invalidation of the session will also cause the request to
|
||||||
|
* redirect to the URL specified, and a {@link
|
||||||
|
* net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent} to be published
|
||||||
|
* via the {@link net.sf.acegisecurity.ui.session.HttpSessionEventPublisher}
|
||||||
|
* registered in <code>web.xml</code>.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class ConcurrentSessionFilter implements Filter,
|
||||||
|
InitializingBean {
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private SessionRegistry sessionRegistry;
|
||||||
|
private String expiredUrl;
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public void setExpiredUrl(String expiredUrl) {
|
||||||
|
this.expiredUrl = expiredUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSessionRegistry(SessionRegistry sessionRegistry) {
|
||||||
|
this.sessionRegistry = sessionRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void afterPropertiesSet() throws Exception {
|
||||||
|
Assert.notNull(sessionRegistry, "SessionRegistry required");
|
||||||
|
Assert.hasText(expiredUrl, "ExpiredUrl required");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing. We use IoC container lifecycle services instead.
|
||||||
|
*/
|
||||||
|
public void destroy() {}
|
||||||
|
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response,
|
||||||
|
FilterChain chain) throws IOException, ServletException {
|
||||||
|
Assert.isInstanceOf(HttpServletRequest.class, request,
|
||||||
|
"Can only process HttpServletRequest");
|
||||||
|
Assert.isInstanceOf(HttpServletResponse.class, response,
|
||||||
|
"Can only process HttpServletResponse");
|
||||||
|
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
|
||||||
|
HttpSession session = httpRequest.getSession(false);
|
||||||
|
|
||||||
|
if (session != null) {
|
||||||
|
SessionInformation info = sessionRegistry.getSessionInformation(session
|
||||||
|
.getId());
|
||||||
|
|
||||||
|
if (info != null) {
|
||||||
|
if (info.isExpired()) {
|
||||||
|
// Expired - abort processing
|
||||||
|
session.invalidate();
|
||||||
|
|
||||||
|
String targetUrl = httpRequest.getContextPath()
|
||||||
|
+ expiredUrl;
|
||||||
|
httpResponse.sendRedirect(httpResponse.encodeRedirectURL(
|
||||||
|
targetUrl));
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Non-expired - update last request date/time
|
||||||
|
info.refreshLastRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does nothing. We use IoC container lifecycle services instead.
|
||||||
|
*
|
||||||
|
* @param arg0 ignored
|
||||||
|
*
|
||||||
|
* @throws ServletException ignored
|
||||||
|
*/
|
||||||
|
public void init(FilterConfig arg0) throws ServletException {}
|
||||||
|
}
|
|
@ -13,24 +13,25 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package net.sf.acegisecurity.providers;
|
package net.sf.acegisecurity.concurrent;
|
||||||
|
|
||||||
import net.sf.acegisecurity.Authentication;
|
import net.sf.acegisecurity.Authentication;
|
||||||
import net.sf.acegisecurity.AuthenticationException;
|
import net.sf.acegisecurity.AuthenticationException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See: {@link ConcurrentSessionControllerImpl}
|
* No-op implementation of {@link
|
||||||
|
* net.sf.acegisecurity.concurrent.ConcurrentSessionController}.
|
||||||
*
|
*
|
||||||
* @author Ray Krueger
|
* @author Ben Alex
|
||||||
* @see ConcurrentSessionControllerImpl
|
* @version $Id$
|
||||||
*/
|
*/
|
||||||
public interface ConcurrentSessionController {
|
public class NullConcurrentSessionController
|
||||||
|
implements ConcurrentSessionController {
|
||||||
//~ Methods ================================================================
|
//~ Methods ================================================================
|
||||||
|
|
||||||
void afterAuthentication(Authentication initialAuth, Authentication result)
|
public void checkAuthenticationAllowed(Authentication request)
|
||||||
throws AuthenticationException;
|
throws AuthenticationException {}
|
||||||
|
|
||||||
void beforeAuthentication(Authentication initialAuth)
|
public void registerSuccessfulAuthentication(Authentication authentication) {}
|
||||||
throws AuthenticationException;
|
|
||||||
}
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.AuthenticationException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown by a <code>SessionRegistry</code> implementation if
|
||||||
|
* an attempt is made to create new session information for an existing
|
||||||
|
* sessionId. The user should firstly clear the existing session from the
|
||||||
|
* <code>ConcurrentSessionRegistry</code>.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
*/
|
||||||
|
public class SessionAlreadyUsedException extends AuthenticationException {
|
||||||
|
public SessionAlreadyUsedException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a record of a session within the Acegi Security framework.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* This is primarily used for concurrent session support.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* Sessions have three states: active, expired, and destroyed. A session can
|
||||||
|
* that is invalidated by <code>session.invalidate()</code> or via Servlet
|
||||||
|
* Container management is considered "destroyed". An "expired" session, on
|
||||||
|
* the other hand, is a session that Acegi Security wants to end because it
|
||||||
|
* was selected for removal for some reason (generally as it was the least
|
||||||
|
* recently used session and the maximum sessions for the user were reached).
|
||||||
|
* An "expired" session is removed as soon as possible by a
|
||||||
|
* <code>Filter</code>.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class SessionInformation {
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private Date lastRequest;
|
||||||
|
private Object principal;
|
||||||
|
private String sessionId;
|
||||||
|
private boolean expired = false;
|
||||||
|
|
||||||
|
//~ Constructors ===========================================================
|
||||||
|
|
||||||
|
public SessionInformation(Object principal, String sessionId,
|
||||||
|
Date lastRequest) {
|
||||||
|
Assert.notNull(principal, "Principal required");
|
||||||
|
Assert.hasText(sessionId, "SessionId required");
|
||||||
|
Assert.notNull(lastRequest, "LastRequest required");
|
||||||
|
this.principal = principal;
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
this.lastRequest = lastRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SessionInformation() {}
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public boolean isExpired() {
|
||||||
|
return expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getLastRequest() {
|
||||||
|
return lastRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSessionId() {
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expireNow() {
|
||||||
|
this.expired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the internal lastRequest to the current date and time.
|
||||||
|
*/
|
||||||
|
public void refreshLastRequest() {
|
||||||
|
this.lastRequest = new Date();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintains a registry of <code>SessionInformation</code> instances.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public interface SessionRegistry {
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains all the known sessions for the specified principal. Sessions
|
||||||
|
* that have expired or destroyed are not returned.
|
||||||
|
*
|
||||||
|
* @param principal to locate sessions for (should never be
|
||||||
|
* <code>null</code>)
|
||||||
|
*
|
||||||
|
* @return the unexpired and undestroyed sessions for this principal, or
|
||||||
|
* <code>null</code> if none were found
|
||||||
|
*/
|
||||||
|
public SessionInformation[] getAllSessions(Object principal);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains the session information for the specified
|
||||||
|
* <code>sessionId</code>. Even expired sessions are returned (although
|
||||||
|
* destroyed sessions are never returned).
|
||||||
|
*
|
||||||
|
* @param sessionId to lookup (should never be <code>null</code>)
|
||||||
|
*
|
||||||
|
* @return the session information, or <code>null</code> if not found
|
||||||
|
*/
|
||||||
|
public SessionInformation getSessionInformation(String sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the given <code>sessionId</code> so its last request time is
|
||||||
|
* equal to the present date and time. Silently returns if the given
|
||||||
|
* <code>sessionId</code> cannot be found or the session is marked to expire.
|
||||||
|
*
|
||||||
|
* @param sessionId for which to update the date and time of the last
|
||||||
|
* request (should never be <code>null</code>)
|
||||||
|
*/
|
||||||
|
public void refreshLastRequest(String sessionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new session for the specified principal. The newly
|
||||||
|
* registered session will not be marked for expiration.
|
||||||
|
*
|
||||||
|
* @param sessionId to associate with the principal (should never be
|
||||||
|
* <code>null</code>)
|
||||||
|
* @param principal to associate with the session (should never be
|
||||||
|
* <code>null</code>)
|
||||||
|
*
|
||||||
|
* @throws SessionAlreadyUsedException DOCUMENT ME!
|
||||||
|
*/
|
||||||
|
public void registerNewSession(String sessionId, Object principal)
|
||||||
|
throws SessionAlreadyUsedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all the session information being maintained for the specified
|
||||||
|
* <code>sessionId</code>. If the <code>sessionId</code> is not found, the
|
||||||
|
* method gracefully returns.
|
||||||
|
*
|
||||||
|
* @param sessionId to delete information for (should never be
|
||||||
|
* <code>null</code>)
|
||||||
|
*/
|
||||||
|
public void removeSessionInformation(String sessionId);
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base implementation of {@link
|
||||||
|
* net.sf.acegisecurity.concurrent.SessionRegistry} which also listens for
|
||||||
|
* {@link net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent}s
|
||||||
|
* published in the Spring application context.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* NB: It is important that you register the {@link
|
||||||
|
* net.sf.acegisecurity.ui.session.HttpSessionEventPublisher} in
|
||||||
|
* <code>web.xml</code> so that this class is notified of sessions that
|
||||||
|
* expire.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id${date}
|
||||||
|
*/
|
||||||
|
public class SessionRegistryImpl implements SessionRegistry,
|
||||||
|
ApplicationListener {
|
||||||
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private Map principals = Collections.synchronizedMap(new HashMap()); // <principal:Object,SessionIdSet>
|
||||||
|
private Map sessionIds = Collections.synchronizedMap(new HashMap()); // <sessionId:Object,SessionInformation>
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public SessionInformation[] getAllSessions(Object principal) {
|
||||||
|
Set sessionsUsedByPrincipal = (Set) principals.get(principal);
|
||||||
|
|
||||||
|
if (sessionsUsedByPrincipal == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List list = new ArrayList();
|
||||||
|
Iterator iter = sessionsUsedByPrincipal.iterator();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
String sessionId = (String) iter.next();
|
||||||
|
list.add(getSessionInformation(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (SessionInformation[]) list.toArray(new SessionInformation[] {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionInformation getSessionInformation(String sessionId) {
|
||||||
|
Assert.hasText(sessionId, "SessionId required as per inerface contract");
|
||||||
|
|
||||||
|
return (SessionInformation) sessionIds.get(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onApplicationEvent(ApplicationEvent event) {
|
||||||
|
if (event instanceof HttpSessionDestroyedEvent) {
|
||||||
|
String sessionId = ((HttpSession) event.getSource()).getId();
|
||||||
|
removeSessionInformation(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshLastRequest(String sessionId) {
|
||||||
|
Assert.hasText(sessionId, "SessionId required as per inerface contract");
|
||||||
|
|
||||||
|
SessionInformation info = getSessionInformation(sessionId);
|
||||||
|
|
||||||
|
if (info != null) {
|
||||||
|
info.refreshLastRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerNewSession(String sessionId, Object principal)
|
||||||
|
throws SessionAlreadyUsedException {
|
||||||
|
Assert.hasText(sessionId, "SessionId required as per inerface contract");
|
||||||
|
Assert.notNull(principal, "Principal required as per inerface contract");
|
||||||
|
|
||||||
|
if (getSessionInformation(sessionId) != null) {
|
||||||
|
throw new SessionAlreadyUsedException("Session " + sessionId
|
||||||
|
+ " is already is use");
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionIds.put(sessionId,
|
||||||
|
new SessionInformation(principal, sessionId, new Date()));
|
||||||
|
|
||||||
|
Set sessionsUsedByPrincipal = (Set) principals.get(principal);
|
||||||
|
|
||||||
|
if (sessionsUsedByPrincipal == null) {
|
||||||
|
sessionsUsedByPrincipal = Collections.synchronizedSet(new HashSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionsUsedByPrincipal.add(sessionId);
|
||||||
|
|
||||||
|
principals.put(principal, sessionsUsedByPrincipal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSessionInformation(String sessionId) {
|
||||||
|
Assert.hasText(sessionId, "SessionId required as per inerface contract");
|
||||||
|
|
||||||
|
SessionInformation info = getSessionInformation(sessionId);
|
||||||
|
|
||||||
|
if (info != null) {
|
||||||
|
sessionIds.remove(sessionId);
|
||||||
|
|
||||||
|
Set sessionsUsedByPrincipal = (Set) principals.get(info
|
||||||
|
.getPrincipal());
|
||||||
|
|
||||||
|
if (sessionsUsedByPrincipal != null) {
|
||||||
|
sessionsUsedByPrincipal.remove(sessionId);
|
||||||
|
|
||||||
|
if (sessionsUsedByPrincipal.size() == 0) {
|
||||||
|
// No need to keep pbject in principals Map anymore
|
||||||
|
principals.remove(info.getPrincipal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.Authentication;
|
||||||
|
import net.sf.acegisecurity.UserDetails;
|
||||||
|
import net.sf.acegisecurity.ui.WebAuthenticationDetails;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods to assist with concurrent session management.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class SessionRegistryUtils {
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public static Object obtainPrincipalFromAuthentication(Authentication auth) {
|
||||||
|
Assert.notNull(auth, "Authentication required");
|
||||||
|
Assert.notNull(auth.getPrincipal(),
|
||||||
|
"Authentication.getPrincipal() required");
|
||||||
|
|
||||||
|
if (auth.getPrincipal() instanceof UserDetails) {
|
||||||
|
return ((UserDetails) auth.getPrincipal()).getUsername();
|
||||||
|
} else {
|
||||||
|
return auth.getPrincipal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String obtainSessionIdFromAuthentication(Authentication auth) {
|
||||||
|
Assert.notNull(auth, "Authentication required");
|
||||||
|
Assert.notNull(auth.getDetails(), "Authentication.getDetails() required");
|
||||||
|
Assert.isInstanceOf(WebAuthenticationDetails.class, auth.getDetails());
|
||||||
|
|
||||||
|
String sessionId = ((WebAuthenticationDetails) auth.getDetails())
|
||||||
|
.getSessionId();
|
||||||
|
Assert.hasText(sessionId, "WebAuthenticationDetails missing SessionId");
|
||||||
|
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,312 +0,0 @@
|
||||||
/* 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.beans.BeansException;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.ApplicationContextAware;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpSession;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, ApplicationListener,
|
|
||||||
ApplicationContextAware {
|
|
||||||
//~ Instance fields ========================================================
|
|
||||||
|
|
||||||
protected Map principalsToSessions = new HashMap();
|
|
||||||
protected Map sessionsToPrincipals = new HashMap();
|
|
||||||
protected Set sessionSet = new HashSet();
|
|
||||||
private ApplicationContext applicationContext;
|
|
||||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
|
||||||
private int maxSessions = 1;
|
|
||||||
|
|
||||||
//~ Methods ================================================================
|
|
||||||
|
|
||||||
public void setApplicationContext(ApplicationContext applicationContext)
|
|
||||||
throws BeansException {
|
|
||||||
this.applicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApplicationContext getApplicationContext() {
|
|
||||||
return applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* @throws ConcurrentLoginException If the user is already logged in the
|
|
||||||
* maximum number of times
|
|
||||||
* @see #determineSessionPrincipal(net.sf.acegisecurity.Authentication)
|
|
||||||
*/
|
|
||||||
public void afterAuthentication(Authentication request,
|
|
||||||
Authentication response) throws ConcurrentLoginException {
|
|
||||||
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 is already logged in the
|
|
||||||
* maximum number of times #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
|
|
||||||
* -> 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, > 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)) {
|
|
||||||
//Publish the event
|
|
||||||
publishViolationEvent(request);
|
|
||||||
|
|
||||||
//The user is AT their max, toss them out
|
|
||||||
throw new ConcurrentLoginException(principal
|
|
||||||
+ " has reached the maximum concurrent logins");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publish the even to the application context.
|
|
||||||
* The default action is to publish a new {@link ConcurrentSessionViolationEvent}
|
|
||||||
*
|
|
||||||
* @param auth The authentication object that caused the violation
|
|
||||||
*/
|
|
||||||
protected void publishViolationEvent(Authentication auth) {
|
|
||||||
getApplicationContext().publishEvent(new ConcurrentSessionViolationEvent(auth));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/* 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 org.springframework.context.ApplicationEvent;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Published by the ConcurrentSessionControllerImpl to notify the application
|
|
||||||
* context that a user has attempted to login more than the maximum times
|
|
||||||
* allowed by the {@link ConcurrentSessionControllerImpl#setMaxSessions(int)}
|
|
||||||
*
|
|
||||||
* @author Ray Krueger
|
|
||||||
*/
|
|
||||||
public class ConcurrentSessionViolationEvent extends ApplicationEvent {
|
|
||||||
//~ Constructors ===========================================================
|
|
||||||
|
|
||||||
public ConcurrentSessionViolationEvent(Authentication auth) {
|
|
||||||
super(auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~ Methods ================================================================
|
|
||||||
|
|
||||||
public Authentication getAuthentication() {
|
|
||||||
return (Authentication) getSource();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/* 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,6 +18,8 @@ package net.sf.acegisecurity.providers;
|
||||||
import net.sf.acegisecurity.AbstractAuthenticationManager;
|
import net.sf.acegisecurity.AbstractAuthenticationManager;
|
||||||
import net.sf.acegisecurity.Authentication;
|
import net.sf.acegisecurity.Authentication;
|
||||||
import net.sf.acegisecurity.AuthenticationException;
|
import net.sf.acegisecurity.AuthenticationException;
|
||||||
|
import net.sf.acegisecurity.concurrent.ConcurrentSessionController;
|
||||||
|
import net.sf.acegisecurity.concurrent.NullConcurrentSessionController;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -146,7 +148,7 @@ public class ProviderManager extends AbstractAuthenticationManager
|
||||||
|
|
||||||
Class toTest = authentication.getClass();
|
Class toTest = authentication.getClass();
|
||||||
|
|
||||||
sessionController.beforeAuthentication(authentication);
|
sessionController.checkAuthenticationAllowed(authentication);
|
||||||
|
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
AuthenticationProvider provider = (AuthenticationProvider) iter
|
AuthenticationProvider provider = (AuthenticationProvider) iter
|
||||||
|
@ -159,7 +161,7 @@ public class ProviderManager extends AbstractAuthenticationManager
|
||||||
Authentication result = provider.authenticate(authentication);
|
Authentication result = provider.authenticate(authentication);
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
sessionController.afterAuthentication(authentication, result);
|
sessionController.registerSuccessfulAuthentication(result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.Authentication;
|
||||||
|
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||||
|
import net.sf.acegisecurity.ui.WebAuthenticationDetails;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link ConcurrentSessionControllerImpl}.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class ConcurrentSessionControllerImplTests extends TestCase {
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
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 = ((WebAuthenticationDetails) 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 = ((WebAuthenticationDetails) auth3.getDetails())
|
||||||
|
.getSessionId();
|
||||||
|
assertTrue(registry.getSessionInformation(sessionId1).isExpired());
|
||||||
|
assertFalse(registry.getSessionInformation(sessionId3).isExpired());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testStartupDetectsInvalidMaximumSessions()
|
||||||
|
throws Exception {
|
||||||
|
ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
|
||||||
|
sc.setMaximumSessions(0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
sc.afterPropertiesSet();
|
||||||
|
fail("Should have thrown IAE");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testStartupDetectsInvalidSessionRegistry()
|
||||||
|
throws Exception {
|
||||||
|
ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
|
||||||
|
sc.setSessionRegistry(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
sc.afterPropertiesSet();
|
||||||
|
fail("Should have thrown IAE");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Authentication createAuthentication(String user, String password) {
|
||||||
|
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
|
||||||
|
password);
|
||||||
|
auth.setDetails(createWebDetails(auth));
|
||||||
|
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WebAuthenticationDetails createWebDetails(Authentication auth) {
|
||||||
|
MockHttpSession session = new MockHttpSession();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setSession(session);
|
||||||
|
request.setUserPrincipal(auth);
|
||||||
|
|
||||||
|
return new WebAuthenticationDetails(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockFilterConfig;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link ConcurrentSessionFilter}.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class ConcurrentSessionFilterTests extends TestCase {
|
||||||
|
//~ Constructors ===========================================================
|
||||||
|
|
||||||
|
public ConcurrentSessionFilterTests() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConcurrentSessionFilterTests(String arg0) {
|
||||||
|
super(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
junit.textui.TestRunner.run(ConcurrentSessionFilterTests.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDetectsExpiredSessions() throws Exception {
|
||||||
|
// Setup our HTTP request
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpSession session = new MockHttpSession();
|
||||||
|
request.setSession(session);
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
MockFilterConfig config = new MockFilterConfig(null);
|
||||||
|
|
||||||
|
// Setup our expectation that the filter chain will not be invoked, as we redirect to expiredUrl
|
||||||
|
MockFilterChain chain = new MockFilterChain(false);
|
||||||
|
|
||||||
|
// Setup our test fixture and registry to want this session to be expired
|
||||||
|
ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
|
||||||
|
SessionRegistry registry = new SessionRegistryImpl();
|
||||||
|
registry.registerNewSession(session.getId(), "principal");
|
||||||
|
registry.getSessionInformation(session.getId()).expireNow();
|
||||||
|
filter.setSessionRegistry(registry);
|
||||||
|
filter.setExpiredUrl("/expired.jsp");
|
||||||
|
|
||||||
|
// Test
|
||||||
|
executeFilterInContainerSimulator(config, filter, request, response,
|
||||||
|
chain);
|
||||||
|
|
||||||
|
assertEquals("/expired.jsp", response.getRedirectedUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDetectsMissingExpiredUrl() throws Exception {
|
||||||
|
ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
|
||||||
|
filter.setSessionRegistry(new SessionRegistryImpl());
|
||||||
|
|
||||||
|
try {
|
||||||
|
filter.afterPropertiesSet();
|
||||||
|
fail("Should have thrown IAE");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDetectsMissingSessionRegistry() throws Exception {
|
||||||
|
ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
|
||||||
|
filter.setExpiredUrl("xcx");
|
||||||
|
|
||||||
|
try {
|
||||||
|
filter.afterPropertiesSet();
|
||||||
|
fail("Should have thrown IAE");
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUpdatesLastRequestTime() throws Exception {
|
||||||
|
// Setup our HTTP request
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpSession session = new MockHttpSession();
|
||||||
|
request.setSession(session);
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
MockFilterConfig config = new MockFilterConfig(null);
|
||||||
|
|
||||||
|
// Setup our expectation that the filter chain will be invoked, as our session hasn't expired
|
||||||
|
MockFilterChain chain = new MockFilterChain(true);
|
||||||
|
|
||||||
|
// Setup our test fixture
|
||||||
|
ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
|
||||||
|
SessionRegistry registry = new SessionRegistryImpl();
|
||||||
|
registry.registerNewSession(session.getId(), "principal");
|
||||||
|
|
||||||
|
Date lastRequest = registry.getSessionInformation(session.getId())
|
||||||
|
.getLastRequest();
|
||||||
|
filter.setSessionRegistry(registry);
|
||||||
|
filter.setExpiredUrl("/expired.jsp");
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
// Test
|
||||||
|
executeFilterInContainerSimulator(config, filter, request, response,
|
||||||
|
chain);
|
||||||
|
|
||||||
|
assertTrue(registry.getSessionInformation(session.getId())
|
||||||
|
.getLastRequest().after(lastRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeFilterInContainerSimulator(FilterConfig filterConfig,
|
||||||
|
Filter filter, ServletRequest request, ServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
filter.init(filterConfig);
|
||||||
|
filter.doFilter(request, response, filterChain);
|
||||||
|
filter.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
//~ Inner Classes ==========================================================
|
||||||
|
|
||||||
|
private class MockFilterChain implements FilterChain {
|
||||||
|
private boolean expectToProceed;
|
||||||
|
|
||||||
|
public MockFilterChain(boolean expectToProceed) {
|
||||||
|
this.expectToProceed = expectToProceed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockFilterChain() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
if (expectToProceed) {
|
||||||
|
assertTrue(true);
|
||||||
|
} else {
|
||||||
|
fail("Did not expect filter chain to proceed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SessionInformation}.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class SessionInformationTests extends TestCase {
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public void testObject() throws Exception {
|
||||||
|
Object principal = "Some principal object";
|
||||||
|
String sessionId = "1234567890";
|
||||||
|
Date currentDate = new Date();
|
||||||
|
|
||||||
|
SessionInformation info = new SessionInformation(principal, sessionId,
|
||||||
|
currentDate);
|
||||||
|
assertEquals(principal, info.getPrincipal());
|
||||||
|
assertEquals(sessionId, info.getSessionId());
|
||||||
|
assertEquals(currentDate, info.getLastRequest());
|
||||||
|
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
info.refreshLastRequest();
|
||||||
|
|
||||||
|
assertTrue(info.getLastRequest().after(currentDate));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
/* 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.concurrent;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link SessionRegistryImpl}.
|
||||||
|
*
|
||||||
|
* @author Ben Alex
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public class SessionRegistryImplTests extends TestCase {
|
||||||
|
//~ Methods ================================================================
|
||||||
|
|
||||||
|
public void testEventPublishing() {
|
||||||
|
MockHttpSession httpSession = new MockHttpSession();
|
||||||
|
Object principal = "Some principal object";
|
||||||
|
String sessionId = httpSession.getId();
|
||||||
|
assertNotNull(sessionId);
|
||||||
|
|
||||||
|
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
|
||||||
|
|
||||||
|
// Register new Session
|
||||||
|
sessionRegistry.registerNewSession(sessionId, principal);
|
||||||
|
|
||||||
|
// Deregister session via an ApplicationEvent
|
||||||
|
sessionRegistry.onApplicationEvent(new HttpSessionDestroyedEvent(
|
||||||
|
httpSession));
|
||||||
|
|
||||||
|
// Check attempts to retrieve cleared session return null
|
||||||
|
assertNull(sessionRegistry.getSessionInformation(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSessionInformationLifecycle() throws Exception {
|
||||||
|
Object principal = "Some principal object";
|
||||||
|
String sessionId = "1234567890";
|
||||||
|
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
|
||||||
|
|
||||||
|
// Register new Session
|
||||||
|
sessionRegistry.registerNewSession(sessionId, principal);
|
||||||
|
|
||||||
|
// Retrieve existing session by session ID
|
||||||
|
Date currentDateTime = sessionRegistry.getSessionInformation(sessionId)
|
||||||
|
.getLastRequest();
|
||||||
|
assertEquals(principal,
|
||||||
|
sessionRegistry.getSessionInformation(sessionId).getPrincipal());
|
||||||
|
assertEquals(sessionId,
|
||||||
|
sessionRegistry.getSessionInformation(sessionId).getSessionId());
|
||||||
|
assertNotNull(sessionRegistry.getSessionInformation(sessionId)
|
||||||
|
.getLastRequest());
|
||||||
|
|
||||||
|
// Retrieve existing session by principal
|
||||||
|
assertEquals(1, sessionRegistry.getAllSessions(principal).length);
|
||||||
|
|
||||||
|
// Sleep to ensure SessionRegistryImpl will update time
|
||||||
|
Thread.sleep(1000);
|
||||||
|
|
||||||
|
// Update request date/time
|
||||||
|
sessionRegistry.refreshLastRequest(sessionId);
|
||||||
|
|
||||||
|
Date retrieved = sessionRegistry.getSessionInformation(sessionId)
|
||||||
|
.getLastRequest();
|
||||||
|
assertTrue(retrieved.after(currentDateTime));
|
||||||
|
|
||||||
|
// Check it retrieves correctly when looked up via principal
|
||||||
|
assertEquals(retrieved,
|
||||||
|
sessionRegistry.getAllSessions(principal)[0].getLastRequest());
|
||||||
|
|
||||||
|
// Clear session information
|
||||||
|
sessionRegistry.removeSessionInformation(sessionId);
|
||||||
|
|
||||||
|
// Check attempts to retrieve cleared session return null
|
||||||
|
assertNull(sessionRegistry.getSessionInformation(sessionId));
|
||||||
|
assertNull(sessionRegistry.getAllSessions(principal));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTwoSessionsOnePrincipalHandling() throws Exception {
|
||||||
|
Object principal = "Some principal object";
|
||||||
|
String sessionId1 = "1234567890";
|
||||||
|
String sessionId2 = "9876543210";
|
||||||
|
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
|
||||||
|
|
||||||
|
// Register new Session
|
||||||
|
sessionRegistry.registerNewSession(sessionId1, principal);
|
||||||
|
assertEquals(1, sessionRegistry.getAllSessions(principal).length);
|
||||||
|
assertEquals(sessionId1,
|
||||||
|
sessionRegistry.getAllSessions(principal)[0].getSessionId());
|
||||||
|
|
||||||
|
// Register new Session
|
||||||
|
sessionRegistry.registerNewSession(sessionId2, principal);
|
||||||
|
assertEquals(2, sessionRegistry.getAllSessions(principal).length);
|
||||||
|
assertEquals(sessionId2,
|
||||||
|
sessionRegistry.getAllSessions(principal)[1].getSessionId());
|
||||||
|
|
||||||
|
// Clear session information
|
||||||
|
sessionRegistry.removeSessionInformation(sessionId1);
|
||||||
|
assertEquals(1, sessionRegistry.getAllSessions(principal).length);
|
||||||
|
assertEquals(sessionId2,
|
||||||
|
sessionRegistry.getAllSessions(principal)[0].getSessionId());
|
||||||
|
|
||||||
|
// Clear final session
|
||||||
|
sessionRegistry.removeSessionInformation(sessionId2);
|
||||||
|
assertNull(sessionRegistry.getSessionInformation(sessionId2));
|
||||||
|
assertNull(sessionRegistry.getAllSessions(principal));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTwoSessionsOnePrincipalExpiring() throws Exception {
|
||||||
|
Object principal = "Some principal object";
|
||||||
|
String sessionId1 = "1234567890";
|
||||||
|
String sessionId2 = "9876543210";
|
||||||
|
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
|
||||||
|
|
||||||
|
// Register new Session
|
||||||
|
sessionRegistry.registerNewSession(sessionId1, principal);
|
||||||
|
assertEquals(1, sessionRegistry.getAllSessions(principal).length);
|
||||||
|
assertEquals(sessionId1,
|
||||||
|
sessionRegistry.getAllSessions(principal)[0].getSessionId());
|
||||||
|
|
||||||
|
// Register new Session
|
||||||
|
sessionRegistry.registerNewSession(sessionId2, principal);
|
||||||
|
assertEquals(2, sessionRegistry.getAllSessions(principal).length);
|
||||||
|
assertEquals(sessionId2,
|
||||||
|
sessionRegistry.getAllSessions(principal)[1].getSessionId());
|
||||||
|
|
||||||
|
// Expire one session
|
||||||
|
SessionInformation session = sessionRegistry.getSessionInformation(sessionId2);
|
||||||
|
session.expireNow();
|
||||||
|
|
||||||
|
// Check retrieval still correct
|
||||||
|
assertTrue(sessionRegistry.getSessionInformation(sessionId2).isExpired());
|
||||||
|
assertFalse(sessionRegistry.getSessionInformation(sessionId1).isExpired());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,280 +0,0 @@
|
||||||
/* 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.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 net.sf.acegisecurity.GrantedAuthority;
|
|
||||||
import net.sf.acegisecurity.GrantedAuthorityImpl;
|
|
||||||
import net.sf.acegisecurity.Authentication;
|
|
||||||
import net.sf.acegisecurity.UserDetails;
|
|
||||||
import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
|
|
||||||
import net.sf.acegisecurity.MockApplicationContext;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.mock.web.MockHttpSession;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
|
|
||||||
import java.security.Principal;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link ConcurrentSessionControllerImpl}
|
|
||||||
*
|
|
||||||
* @author Ray Krueger
|
|
||||||
* @author Luke Taylor
|
|
||||||
* @version $Id$
|
|
||||||
*/
|
|
||||||
public class ConcurrentSessionControllerImplTests extends TestCase {
|
|
||||||
//~ Instance fields ========================================================
|
|
||||||
|
|
||||||
ConcurrentSessionControllerImpl target;
|
|
||||||
|
|
||||||
//~ Methods ================================================================
|
|
||||||
|
|
||||||
public void testAnonymous() throws Exception {
|
|
||||||
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken("blah",
|
|
||||||
"anon",
|
|
||||||
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ANON")});
|
|
||||||
target.beforeAuthentication(auth);
|
|
||||||
target.afterAuthentication(auth, auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBumpCoverage() throws Exception {
|
|
||||||
target.onApplicationEvent(new HttpSessionCreatedEvent(
|
|
||||||
new MockHttpSession()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEnforcementKnownGood() throws Exception {
|
|
||||||
Authentication auth = createAuthentication("user", "password");
|
|
||||||
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++) { // creates 5 sessions
|
|
||||||
auth = createAuthentication("user", "password");
|
|
||||||
target.beforeAuthentication(auth);
|
|
||||||
target.afterAuthentication(auth, auth);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
auth = createAuthentication("user", "password");
|
|
||||||
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");
|
|
||||||
|
|
||||||
target.beforeAuthentication(auth);
|
|
||||||
target.afterAuthentication(auth, auth);
|
|
||||||
|
|
||||||
try {
|
|
||||||
target.beforeAuthentication(createAuthentication("user", "password"));
|
|
||||||
fail(
|
|
||||||
"Only allowed 1 session, this should have thrown a ConcurrentLoginException");
|
|
||||||
} catch (ConcurrentLoginException e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEnforcementUnlimitedSameSession() throws Exception {
|
|
||||||
target.setMaxSessions(1);
|
|
||||||
MockHttpSession session = new MockHttpSession(); // all requests are within this session
|
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("user",
|
|
||||||
"password");
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setSession(session);
|
|
||||||
request.setUserPrincipal(auth);
|
|
||||||
auth.setDetails(new WebAuthenticationDetails(request));
|
|
||||||
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");
|
|
||||||
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();
|
|
||||||
request.setSession(session);
|
|
||||||
request.setUserPrincipal(auth);
|
|
||||||
auth.setDetails(new WebAuthenticationDetails(request));
|
|
||||||
|
|
||||||
target.beforeAuthentication(auth);
|
|
||||||
target.afterAuthentication(auth, auth);
|
|
||||||
|
|
||||||
target.onApplicationEvent(new HttpSessionDestroyedEvent(session));
|
|
||||||
|
|
||||||
Authentication different = createAuthentication("user", "password");
|
|
||||||
target.beforeAuthentication(different);
|
|
||||||
target.afterAuthentication(different, different);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEventObject() {
|
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("user",
|
|
||||||
"password");
|
|
||||||
ConcurrentSessionViolationEvent ev = new ConcurrentSessionViolationEvent(token);
|
|
||||||
assertEquals("The token that went in should be the token that comes out",
|
|
||||||
token, ev.getAuthentication());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testImplementsApplicationListener() throws Exception {
|
|
||||||
assertTrue("This class must implement ApplicationListener, and at one point it didn't.",
|
|
||||||
target instanceof ApplicationListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
true, new GrantedAuthority[0]);
|
|
||||||
final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
|
|
||||||
"password", user.getAuthorities());
|
|
||||||
auth.setDetails(createWebDetails(auth));
|
|
||||||
|
|
||||||
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));
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setUp() throws Exception {
|
|
||||||
target = new ConcurrentSessionControllerImpl();
|
|
||||||
target.setApplicationContext(MockApplicationContext.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Authentication createAuthentication(String user, String password) {
|
|
||||||
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
|
|
||||||
password);
|
|
||||||
auth.setDetails(createWebDetails(auth));
|
|
||||||
|
|
||||||
return auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private WebAuthenticationDetails createWebDetails(Authentication auth) {
|
|
||||||
MockHttpSession session = new MockHttpSession();
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
|
||||||
request.setSession(session);
|
|
||||||
request.setUserPrincipal(auth);
|
|
||||||
|
|
||||||
return new WebAuthenticationDetails(request);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,6 +18,8 @@ package net.sf.acegisecurity.providers;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import net.sf.acegisecurity.*;
|
import net.sf.acegisecurity.*;
|
||||||
|
import net.sf.acegisecurity.concurrent.ConcurrentSessionControllerImpl;
|
||||||
|
import net.sf.acegisecurity.concurrent.NullConcurrentSessionController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
Loading…
Reference in New Issue