001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.shiro.session.mgt; 020 021import org.apache.shiro.authz.AuthorizationException; 022import org.apache.shiro.session.ExpiredSessionException; 023import org.apache.shiro.session.InvalidSessionException; 024import org.apache.shiro.session.Session; 025import org.apache.shiro.session.UnknownSessionException; 026import org.apache.shiro.lang.util.Destroyable; 027import org.apache.shiro.lang.util.LifecycleUtils; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import java.util.Collection; 032 033 034/** 035 * Default business-tier implementation of the {@link ValidatingSessionManager} interface. 036 * 037 * @since 0.1 038 */ 039public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager 040 implements ValidatingSessionManager, Destroyable { 041 042 /** 043 * The default interval at which sessions will be validated (1 hour); 044 * This can be overridden by calling {@link #setSessionValidationInterval(long)} 045 */ 046 public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR; 047 048 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractValidatingSessionManager.class); 049 050 protected boolean sessionValidationSchedulerEnabled; 051 052 /** 053 * Scheduler used to validate sessions on a regular basis. 054 */ 055 protected SessionValidationScheduler sessionValidationScheduler; 056 057 protected long sessionValidationInterval; 058 059 public AbstractValidatingSessionManager() { 060 this.sessionValidationSchedulerEnabled = true; 061 this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL; 062 } 063 064 public boolean isSessionValidationSchedulerEnabled() { 065 return sessionValidationSchedulerEnabled; 066 } 067 068 @SuppressWarnings({"UnusedDeclaration"}) 069 public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) { 070 this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled; 071 } 072 073 public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) { 074 this.sessionValidationScheduler = sessionValidationScheduler; 075 } 076 077 public SessionValidationScheduler getSessionValidationScheduler() { 078 return sessionValidationScheduler; 079 } 080 081 private void enableSessionValidationIfNecessary() { 082 SessionValidationScheduler scheduler = getSessionValidationScheduler(); 083 if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) { 084 enableSessionValidation(); 085 } 086 } 087 088 /** 089 * If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the 090 * {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is 091 * never called) , this method allows one to specify how 092 * frequently session should be validated (to check for orphans). The default value is 093 * {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}. 094 * <p/> 095 * If you override the default scheduler, it is assumed that overriding instance 'knows' how often to 096 * validate sessions, and this attribute will be ignored. 097 * <p/> 098 * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}. 099 * 100 * @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans. 101 */ 102 public void setSessionValidationInterval(long sessionValidationInterval) { 103 this.sessionValidationInterval = sessionValidationInterval; 104 } 105 106 public long getSessionValidationInterval() { 107 return sessionValidationInterval; 108 } 109 110 @Override 111 protected final Session doGetSession(final SessionKey key) throws InvalidSessionException { 112 enableSessionValidationIfNecessary(); 113 114 LOGGER.trace("Attempting to retrieve session with key {}", key); 115 116 Session s = retrieveSession(key); 117 if (s != null) { 118 validate(s, key); 119 } 120 return s; 121 } 122 123 /** 124 * Looks up a session from the underlying data store based on the specified session key. 125 * 126 * @param key the session key to use to look up the target session. 127 * @return the session identified by {@code sessionId}. 128 * @throws UnknownSessionException if there is no session identified by {@code sessionId}. 129 */ 130 protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException; 131 132 protected Session createSession(SessionContext context) throws AuthorizationException { 133 enableSessionValidationIfNecessary(); 134 return doCreateSession(context); 135 } 136 137 protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException; 138 139 protected void validate(Session session, SessionKey key) throws InvalidSessionException { 140 try { 141 doValidate(session); 142 } catch (ExpiredSessionException ese) { 143 onExpiration(session, ese, key); 144 throw ese; 145 } catch (InvalidSessionException ise) { 146 onInvalidation(session, ise, key); 147 throw ise; 148 } 149 } 150 151 protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) { 152 LOGGER.trace("Session with id [{}] has expired.", s.getId()); 153 try { 154 onExpiration(s); 155 notifyExpiration(s); 156 } finally { 157 afterExpired(s); 158 } 159 } 160 161 protected void onExpiration(Session session) { 162 onChange(session); 163 } 164 165 protected void afterExpired(Session session) { 166 } 167 168 protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) { 169 if (ise instanceof ExpiredSessionException) { 170 onExpiration(s, (ExpiredSessionException) ise, key); 171 return; 172 } 173 LOGGER.trace("Session with id [{}] is invalid.", s.getId()); 174 try { 175 onStop(s); 176 notifyStop(s); 177 } finally { 178 afterStopped(s); 179 } 180 } 181 182 protected void doValidate(Session session) throws InvalidSessionException { 183 if (session instanceof ValidatingSession) { 184 ((ValidatingSession) session).validate(); 185 } else { 186 String msg = "The " + getClass().getName() + " implementation only supports validating " 187 + "Session implementations of the " + ValidatingSession.class.getName() + " interface. " 188 + "Please either implement this interface in your session implementation or override the " 189 + AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation."; 190 throw new IllegalStateException(msg); 191 } 192 } 193 194 /** 195 * Subclass template hook in case per-session timeout is not based on 196 * {@link org.apache.shiro.session.Session#getTimeout()}. 197 * <p/> 198 * <p>This implementation merely returns {@link org.apache.shiro.session.Session#getTimeout()}</p> 199 * 200 * @param session the session for which to determine session timeout. 201 * @return the time in milliseconds the specified session may remain idle before expiring. 202 */ 203 protected long getTimeout(Session session) { 204 return session.getTimeout(); 205 } 206 207 protected SessionValidationScheduler createSessionValidationScheduler() { 208 ExecutorServiceSessionValidationScheduler scheduler; 209 210 if (LOGGER.isDebugEnabled()) { 211 LOGGER.debug("No sessionValidationScheduler set. Attempting to create default instance."); 212 } 213 scheduler = new ExecutorServiceSessionValidationScheduler(this); 214 scheduler.setSessionValidationInterval(getSessionValidationInterval()); 215 if (LOGGER.isTraceEnabled()) { 216 LOGGER.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "]."); 217 } 218 return scheduler; 219 } 220 221 protected synchronized void enableSessionValidation() { 222 SessionValidationScheduler scheduler = getSessionValidationScheduler(); 223 if (scheduler == null) { 224 scheduler = createSessionValidationScheduler(); 225 setSessionValidationScheduler(scheduler); 226 } 227 // it is possible that that a scheduler was already created and set via 'setSessionValidationScheduler()' 228 // but would not have been enabled/started yet 229 if (!scheduler.isEnabled()) { 230 if (LOGGER.isInfoEnabled()) { 231 LOGGER.info("Enabling session validation scheduler..."); 232 } 233 scheduler.enableSessionValidation(); 234 afterSessionValidationEnabled(); 235 } 236 } 237 238 protected void afterSessionValidationEnabled() { 239 } 240 241 protected synchronized void disableSessionValidation() { 242 beforeSessionValidationDisabled(); 243 SessionValidationScheduler scheduler = getSessionValidationScheduler(); 244 if (scheduler != null) { 245 try { 246 scheduler.disableSessionValidation(); 247 if (LOGGER.isInfoEnabled()) { 248 LOGGER.info("Disabled session validation scheduler."); 249 } 250 } catch (Exception e) { 251 if (LOGGER.isDebugEnabled()) { 252 String msg = "Unable to disable SessionValidationScheduler. Ignoring (shutting down)..."; 253 LOGGER.debug(msg, e); 254 } 255 } 256 LifecycleUtils.destroy(scheduler); 257 setSessionValidationScheduler(null); 258 } 259 } 260 261 protected void beforeSessionValidationDisabled() { 262 } 263 264 public void destroy() { 265 disableSessionValidation(); 266 } 267 268 /** 269 * @see ValidatingSessionManager#validateSessions() 270 */ 271 public void validateSessions() { 272 if (LOGGER.isInfoEnabled()) { 273 LOGGER.info("Validating all active sessions..."); 274 } 275 276 int invalidCount = 0; 277 278 Collection<Session> activeSessions = getActiveSessions(); 279 280 if (activeSessions != null && !activeSessions.isEmpty()) { 281 for (Session s : activeSessions) { 282 try { 283 //simulate a lookup key to satisfy the method signature. 284 //this could probably stand to be cleaned up in future versions: 285 SessionKey key = new DefaultSessionKey(s.getId()); 286 validate(s, key); 287 } catch (InvalidSessionException e) { 288 if (LOGGER.isDebugEnabled()) { 289 boolean expired = (e instanceof ExpiredSessionException); 290 String msg = "Invalidated session with id [" + s.getId() + "]" 291 + (expired ? " (expired)" : " (stopped)"); 292 LOGGER.debug(msg); 293 } 294 invalidCount++; 295 } 296 } 297 } 298 299 if (LOGGER.isInfoEnabled()) { 300 String msg = "Finished session validation."; 301 if (invalidCount > 0) { 302 msg += " [" + invalidCount + "] sessions were stopped."; 303 } else { 304 msg += " No sessions were stopped."; 305 } 306 LOGGER.info(msg); 307 } 308 } 309 310 protected abstract Collection<Session> getActiveSessions(); 311}