001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.concurrent;
017:
018: import org.acegisecurity.AcegiMessageSource;
019: import org.acegisecurity.Authentication;
020: import org.acegisecurity.AuthenticationException;
021:
022: import org.springframework.beans.factory.InitializingBean;
023:
024: import org.springframework.context.MessageSource;
025: import org.springframework.context.MessageSourceAware;
026: import org.springframework.context.support.MessageSourceAccessor;
027:
028: import org.springframework.util.Assert;
029:
030: /**
031: * Base implementation of {@link ConcurrentSessionControllerImpl} which prohibits simultaneous logins.<p>By default
032: * uses {@link SessionRegistryImpl}, although any <code>SessionRegistry</code> may be used.</p>
033: *
034: * @author Ben Alex
035: * @version $Id: ConcurrentSessionControllerImpl.java 2279 2007-12-02 03:00:26Z benalex $
036: */
037: public class ConcurrentSessionControllerImpl implements
038: ConcurrentSessionController, InitializingBean,
039: MessageSourceAware {
040: //~ Instance fields ================================================================================================
041:
042: protected MessageSourceAccessor messages = AcegiMessageSource
043: .getAccessor();
044: private SessionRegistry sessionRegistry;
045: private boolean exceptionIfMaximumExceeded = false;
046: private int maximumSessions = 1;
047:
048: //~ Methods ========================================================================================================
049:
050: public void afterPropertiesSet() throws Exception {
051: Assert.notNull(sessionRegistry, "SessionRegistry required");
052: Assert
053: .isTrue(
054: maximumSessions != 0,
055: "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
056: Assert.notNull(this .messages, "A message source must be set");
057: }
058:
059: /**
060: * Allows subclasses to customise behaviour when too many sessions are detected.
061: *
062: * @param sessionId the session ID of the present request
063: * @param sessions either <code>null</code> or all unexpired sessions associated with the principal
064: * @param allowableSessions DOCUMENT ME!
065: * @param registry an instance of the <code>SessionRegistry</code> for subclass use
066: *
067: * @throws ConcurrentLoginException DOCUMENT ME!
068: */
069: protected void allowableSessionsExceeded(String sessionId,
070: SessionInformation[] sessions, int allowableSessions,
071: SessionRegistry registry) {
072: if (exceptionIfMaximumExceeded || (sessions == null)) {
073: throw new ConcurrentLoginException(
074: messages
075: .getMessage(
076: "ConcurrentSessionControllerImpl.exceededAllowed",
077: new Object[] { new Integer(
078: allowableSessions) },
079: "Maximum sessions of {0} for this principal exceeded"));
080: }
081:
082: // Determine least recently used session, and mark it for invalidation
083: SessionInformation leastRecentlyUsed = null;
084:
085: for (int i = 0; i < sessions.length; i++) {
086: if ((leastRecentlyUsed == null)
087: || sessions[i].getLastRequest().before(
088: leastRecentlyUsed.getLastRequest())) {
089: leastRecentlyUsed = sessions[i];
090: }
091: }
092:
093: leastRecentlyUsed.expireNow();
094: }
095:
096: public void checkAuthenticationAllowed(Authentication request)
097: throws AuthenticationException {
098: Assert
099: .notNull(request,
100: "Authentication request cannot be null (violation of interface contract)");
101:
102: Object principal = SessionRegistryUtils
103: .obtainPrincipalFromAuthentication(request);
104: String sessionId = SessionRegistryUtils
105: .obtainSessionIdFromAuthentication(request);
106:
107: SessionInformation[] sessions = sessionRegistry.getAllSessions(
108: principal, false);
109:
110: int sessionCount = 0;
111:
112: if (sessions != null) {
113: sessionCount = sessions.length;
114: }
115:
116: int allowableSessions = getMaximumSessionsForThisUser(request);
117: Assert
118: .isTrue(
119: allowableSessions != 0,
120: "getMaximumSessionsForThisUser() must return either -1 to allow "
121: + "unlimited logins, or a positive integer to specify a maximum");
122:
123: if (sessionCount < allowableSessions) {
124: // They haven't got too many login sessions running at present
125: return;
126: } else if (allowableSessions == -1) {
127: // We permit unlimited logins
128: return;
129: } else if (sessionCount == allowableSessions) {
130: // Only permit it though if this request is associated with one of the sessions
131: for (int i = 0; i < sessionCount; i++) {
132: if (sessions[i].getSessionId().equals(sessionId)) {
133: return;
134: }
135: }
136: }
137:
138: allowableSessionsExceeded(sessionId, sessions,
139: allowableSessions, sessionRegistry);
140: }
141:
142: /**
143: * Method intended for use by subclasses to override the maximum number of sessions that are permitted for
144: * a particular authentication. The default implementation simply returns the <code>maximumSessions</code> value
145: * for the bean.
146: *
147: * @param authentication to determine the maximum sessions for
148: *
149: * @return either -1 meaning unlimited, or a positive integer to limit (never zero)
150: */
151: protected int getMaximumSessionsForThisUser(
152: Authentication authentication) {
153: return maximumSessions;
154: }
155:
156: public void registerSuccessfulAuthentication(
157: Authentication authentication) {
158: Assert
159: .notNull(authentication,
160: "Authentication cannot be null (violation of interface contract)");
161:
162: Object principal = SessionRegistryUtils
163: .obtainPrincipalFromAuthentication(authentication);
164: String sessionId = SessionRegistryUtils
165: .obtainSessionIdFromAuthentication(authentication);
166:
167: sessionRegistry.registerNewSession(sessionId, principal);
168: }
169:
170: public void setExceptionIfMaximumExceeded(
171: boolean exceptionIfMaximumExceeded) {
172: this .exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
173: }
174:
175: public void setMaximumSessions(int maximumSessions) {
176: this .maximumSessions = maximumSessions;
177: }
178:
179: public void setMessageSource(MessageSource messageSource) {
180: this .messages = new MessageSourceAccessor(messageSource);
181: }
182:
183: public void setSessionRegistry(SessionRegistry sessionRegistry) {
184: this.sessionRegistry = sessionRegistry;
185: }
186: }
|