View Javadoc

1   /* Copyright 2004, 2005 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  import org.springframework.beans.factory.InitializingBean;
22  import org.springframework.context.MessageSource;
23  import org.springframework.context.MessageSourceAware;
24  import org.springframework.context.support.MessageSourceAccessor;
25  import org.springframework.util.Assert;
26  
27  
28  /***
29   * Base implementation of {@link ConcurrentSessionControllerImpl} which
30   * prohibits simultaneous logins.
31   * 
32   * <p>
33   * By default uses {@link org.acegisecurity.concurrent.SessionRegistryImpl},
34   * although any <code>SessionRegistry</code> may be used.
35   * </p>
36   */
37  public class ConcurrentSessionControllerImpl
38      implements ConcurrentSessionController, InitializingBean,
39          MessageSourceAware {
40      //~ Instance fields ========================================================
41  
42      protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
43      private SessionRegistry sessionRegistry = new SessionRegistryImpl();
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
58       * detected.
59       *
60       * @param sessionId the session ID of the present request
61       * @param sessions either <code>null</code> or all unexpired sessions
62       *        associated with the principal
63       * @param allowableSessions DOCUMENT ME!
64       * @param registry an instance of the <code>SessionRegistry</code> for
65       *        subclass use
66       *
67       * @throws ConcurrentLoginException DOCUMENT ME!
68       */
69      protected void allowableSessionsExceeded(String sessionId,
70          SessionInformation[] sessions, int allowableSessions,
71          SessionRegistry registry) {
72          if (exceptionIfMaximumExceeded || (sessions == null)) {
73              throw new ConcurrentLoginException(messages.getMessage(
74                      "ConcurrentSessionControllerImpl.exceededAllowed",
75                      new Object[] {new Integer(allowableSessions)},
76                      "Maximum sessions of {0} for this principal exceeded"));
77          }
78  
79          // Determine least recently used session, and mark it for invalidation
80          SessionInformation leastRecentlyUsed = null;
81  
82          for (int i = 0; i < sessions.length; i++) {
83              if ((leastRecentlyUsed == null)
84                  || sessions[i].getLastRequest()
85                                .before(leastRecentlyUsed.getLastRequest())) {
86                  leastRecentlyUsed = sessions[i];
87              }
88          }
89  
90          leastRecentlyUsed.expireNow();
91      }
92  
93      public void checkAuthenticationAllowed(Authentication request)
94          throws AuthenticationException {
95          Assert.notNull(request,
96              "Authentication request cannot be null (violation of interface contract)");
97  
98          Object principal = SessionRegistryUtils
99                  .obtainPrincipalFromAuthentication(request);
100             String sessionId = SessionRegistryUtils
101                     .obtainSessionIdFromAuthentication(request);
102 
103                 SessionInformation[] sessions = sessionRegistry.getAllSessions(principal);
104 
105                 int sessionCount = 0;
106 
107                 if (sessions != null) {
108                     sessionCount = sessions.length;
109                 }
110 
111                 int allowableSessions = getMaximumSessionsForThisUser(request);
112                 Assert.isTrue(allowableSessions != 0,
113                     "getMaximumSessionsForThisUser() must return either -1 to allow unlimited logins, or a positive integer to specify a maximum");
114 
115                 if (sessionCount < allowableSessions) {
116                     return;
117                 } else if (sessionCount == allowableSessions) {
118                     // Only permit it though if this request is associated with one of the sessions
119                     for (int i = 0; i < sessionCount; i++) {
120                         if (sessions[i].getSessionId().equals(sessionId)) {
121                             return;
122                         }
123                     }
124                 }
125 
126                 allowableSessionsExceeded(sessionId, sessions,
127                     allowableSessions, sessionRegistry);
128             }
129 
130             /***
131              * Method intended for use by subclasses to override the maximum
132              * number of sessions that are permitted for a particular
133              * authentication. The default implementation simply returns the
134              * <code>maximumSessions</code> value for the bean.
135              *
136              * @param authentication to determine the maximum sessions for
137              *
138              * @return either -1 meaning unlimited, or a positive integer to
139              *         limit (never zero)
140              */
141             protected int getMaximumSessionsForThisUser(
142                 Authentication authentication) {
143                 return maximumSessions;
144             }
145 
146             public void registerSuccessfulAuthentication(
147                 Authentication authentication) {
148                 Assert.notNull(authentication,
149                     "Authentication cannot be null (violation of interface contract)");
150 
151                 Object principal = SessionRegistryUtils
152                         .obtainPrincipalFromAuthentication(authentication);
153                     String sessionId = SessionRegistryUtils
154                             .obtainSessionIdFromAuthentication(authentication);
155 
156                         sessionRegistry.removeSessionInformation(sessionId);
157                         sessionRegistry.registerNewSession(sessionId, principal);
158                     }
159 
160                     public void setExceptionIfMaximumExceeded(
161                         boolean exceptionIfMaximumExceeded) {
162                         this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
163                     }
164 
165                     public void setMaximumSessions(int maximumSessions) {
166                         this.maximumSessions = maximumSessions;
167                     }
168 
169                     public void setMessageSource(MessageSource messageSource) {
170                         this.messages = new MessageSourceAccessor(messageSource);
171                     }
172 
173                     public void setSessionRegistry(
174                         SessionRegistry sessionRegistry) {
175                         this.sessionRegistry = sessionRegistry;
176                     }
177                 }