View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.concurrent;
17  
18  import org.acegisecurity.AcegiMessageSource;
19  import org.acegisecurity.Authentication;
20  import org.acegisecurity.AuthenticationException;
21  
22  import org.springframework.beans.factory.InitializingBean;
23  
24  import org.springframework.context.MessageSource;
25  import org.springframework.context.MessageSourceAware;
26  import org.springframework.context.support.MessageSourceAccessor;
27  
28  import org.springframework.util.Assert;
29  
30  
31  /**
32   * Base implementation of {@link ConcurrentSessionControllerImpl} which prohibits simultaneous logins.<p>By default
33   * uses {@link SessionRegistryImpl}, although any <code>SessionRegistry</code> may be used.</p>
34   *
35   * @author Ben Alex
36   * @version $Id: ConcurrentSessionControllerImpl.java 2279 2007-12-02 03:00:26Z benalex $
37   */
38  public class ConcurrentSessionControllerImpl implements ConcurrentSessionController, InitializingBean,
39      MessageSourceAware {
40      //~ Instance fields ================================================================================================
41  
42      protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
43      private SessionRegistry sessionRegistry;
44      private boolean exceptionIfMaximumExceeded = false;
45      private int maximumSessions = 1;
46  
47      //~ Methods ========================================================================================================
48  
49      public void afterPropertiesSet() throws Exception {
50          Assert.notNull(sessionRegistry, "SessionRegistry required");
51          Assert.isTrue(maximumSessions != 0,
52              "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
53          Assert.notNull(this.messages, "A message source must be set");
54      }
55  
56      /**
57       * Allows subclasses to customise behaviour when too many sessions are detected.
58       *
59       * @param sessionId the session ID of the present request
60       * @param sessions either <code>null</code> or all unexpired sessions associated with the principal
61       * @param allowableSessions DOCUMENT ME!
62       * @param registry an instance of the <code>SessionRegistry</code> for subclass use
63       *
64       * @throws ConcurrentLoginException DOCUMENT ME!
65       */
66      protected void allowableSessionsExceeded(String sessionId, SessionInformation[] sessions, int allowableSessions,
67          SessionRegistry registry) {
68          if (exceptionIfMaximumExceeded || (sessions == null)) {
69              throw new ConcurrentLoginException(messages.getMessage("ConcurrentSessionControllerImpl.exceededAllowed",
70                      new Object[] {new Integer(allowableSessions)},
71                      "Maximum sessions of {0} for this principal exceeded"));
72          }
73  
74          // Determine least recently used session, and mark it for invalidation
75          SessionInformation leastRecentlyUsed = null;
76  
77          for (int i = 0; i < sessions.length; i++) {
78              if ((leastRecentlyUsed == null)
79                      || sessions[i].getLastRequest().before(leastRecentlyUsed.getLastRequest())) {
80                  leastRecentlyUsed = sessions[i];
81              }
82          }
83  
84          leastRecentlyUsed.expireNow();
85      }
86  
87      public void checkAuthenticationAllowed(Authentication request)
88          throws AuthenticationException {
89          Assert.notNull(request, "Authentication request cannot be null (violation of interface contract)");
90  
91          Object principal = SessionRegistryUtils.obtainPrincipalFromAuthentication(request);
92          String sessionId = SessionRegistryUtils.obtainSessionIdFromAuthentication(request);
93  
94          SessionInformation[] sessions = sessionRegistry.getAllSessions(principal, false);
95  
96          int sessionCount = 0;
97  
98          if (sessions != null) {
99              sessionCount = sessions.length;
100         }
101 
102         int allowableSessions = getMaximumSessionsForThisUser(request);
103         Assert.isTrue(allowableSessions != 0, "getMaximumSessionsForThisUser() must return either -1 to allow "
104                 + "unlimited logins, or a positive integer to specify a maximum");
105 
106         if (sessionCount < allowableSessions) {
107             // They haven't got too many login sessions running at present
108             return;
109         } else if (allowableSessions == -1) {
110             // We permit unlimited logins
111             return;
112         } else if (sessionCount == allowableSessions) {
113             // Only permit it though if this request is associated with one of the sessions
114             for (int i = 0; i < sessionCount; i++) {
115                 if (sessions[i].getSessionId().equals(sessionId)) {
116                     return;
117                 }
118             }
119         }
120 
121         allowableSessionsExceeded(sessionId, sessions, allowableSessions, sessionRegistry);
122     }
123 
124     /**
125      * Method intended for use by subclasses to override the maximum number of sessions that are permitted for
126      * a particular authentication. The default implementation simply returns the <code>maximumSessions</code> value
127      * for the bean.
128      *
129      * @param authentication to determine the maximum sessions for
130      *
131      * @return either -1 meaning unlimited, or a positive integer to limit (never zero)
132      */
133     protected int getMaximumSessionsForThisUser(Authentication authentication) {
134         return maximumSessions;
135     }
136 
137     public void registerSuccessfulAuthentication(Authentication authentication) {
138         Assert.notNull(authentication, "Authentication cannot be null (violation of interface contract)");
139 
140         Object principal = SessionRegistryUtils.obtainPrincipalFromAuthentication(authentication);
141         String sessionId = SessionRegistryUtils.obtainSessionIdFromAuthentication(authentication);
142 
143         sessionRegistry.registerNewSession(sessionId, principal);
144     }
145 
146     public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
147         this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
148     }
149 
150     public void setMaximumSessions(int maximumSessions) {
151         this.maximumSessions = maximumSessions;
152     }
153 
154     public void setMessageSource(MessageSource messageSource) {
155         this.messages = new MessageSourceAccessor(messageSource);
156     }
157 
158     public void setSessionRegistry(SessionRegistry sessionRegistry) {
159         this.sessionRegistry = sessionRegistry;
160     }
161 }