Shiro, apacheshiro
Shiro Session
Session management is a selling point of Shiro.
Shiro can provide session solutions for any application (from simple command line programs or mobile applications to large enterprise applications.
Before the appearance of Shiro, if we want your application to support the session, we usually rely on the web container or the EJB Session Bean.
Shiro supports sessions more easily and can be used in any application or container.
Even if we use Servlet or EJB, it does not mean that we must use the session of the container. Some features provided by Shiro are sufficient for us to replace them with Shiro session.
- POJO-based
- Easy to customize session persistence
- Container-independent session cluster
- Supports multiple Client Access
- Session event listening
- Extended invalid session
- Transparent Web Support
- Support SSO
When using Shiro session, the methods are the same in both JavaSE and web.
public static void main(String[] args) { Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro/shiro.ini"); SecurityUtils.setSecurityManager(factory.getInstance()); Subject currentUser = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("king","t;stmdtkg"); currentUser.login(token); Session session = currentUser.getSession(); System.out.println(session.getHost()); System.out.println(session.getId()); System.out.println(session.getStartTimestamp()); System.out.println(session.getLastAccessTime()); session.touch(); User u = new User(); session.setAttribute(u, "King."); Iterator<Object> keyItr = session.getAttributeKeys().iterator(); while(keyItr.hasNext()){ System.out.println(session.getAttribute(keyItr.next())); }}
In any environment, you only need to call the getSession () of the Subject.
In addition, the Subject also provides...
Session getSession(boolean create);
That is, whether to create and return a new session when the current Subject session does not exist.
Take DelegatingSubject as an example:
(Note! An isSessionCreationEnabled attribute is added from Shiro 1.2. The default value is true .)
public Session getSession() { return getSession(true);}public Session getSession(boolean create) { if (log.isTraceEnabled()) { log.trace("attempting to get session; create = " + create + "; session is null = " + (this.session == null) + "; session has id = " + (this.session != null && session.getId() != null)); } if (this.session == null && create) { //added in 1.2: if (!isSessionCreationEnabled()) { String msg = "Session creation has been disabled for the current subject. This exception indicates " + "that there is either a programming error (using a session when it should never be " + "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " + "for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " + "for more."; throw new DisabledSessionException(msg); } log.trace("Starting session for host {}", getHost()); SessionContext sessionContext = createSessionContext(); Session session = this.securityManager.start(sessionContext); this.session = decorate(session); } return this.session;}
SessionManager
As in its name, sessionManager is used to manage sessions for Subject in the application, such as creating, deleting, failing, or verifying sessions.
Like other core components in Shiro, it is maintained by SecurityManager.
(Note: public interface SecurityManager extends Authenticator, Authorizer, SessionManager ).
public interface SessionManager { Session start(SessionContext context); Session getSession(SessionKey key) throws SessionException;}
Shiro provides three implementation classes for SessionManager (also sort out the relationship with the SecurityManager implementation class ).
- DefaultSessionManager
- DefaultWebSessionManager
- ServletContainerSessionManager
Specifically, ServletContainerSessionManager is only applicable to servlet containers. To support multiple client accesses, use defawebwebsessionmanager.
By default, sessionManager's implementation class times out for 30 minutes.
See AbstractSessionManager:
public static final long DEFAULT_GLOBAL_SESSION_TIMEOUT = 30 * MILLIS_PER_MINUTE;private long globalSessionTimeout = DEFAULT_GLOBAL_SESSION_TIMEOUT;
Of course, we can also directly set the globalSessionTimeout of AbstractSessionManager.
For example, in. ini:
securityManager.sessionManager.globalSessionTimeout = 3600000
Note! If the SessionManager is ServletContainerSessionManager (which does not inherit AbstractSessionManager), the timeout settings depend on the Servlet container settings.
See: https://issues.apache.org/jira/browse/SHIRO-240
For the verification method of session expiration, refer to SimpleSession:
protected boolean isTimedOut() { if (isExpired()) { return true; } long timeout = getTimeout(); if (timeout >= 0l) { Date lastAccessTime = getLastAccessTime(); if (lastAccessTime == null) { String msg = "session.lastAccessTime for session with id [" + getId() + "] is null. This value must be set at " + "least once, preferably at least upon instantiation. Please check the " + getClass().getName() + " implementation and ensure " + "this value will be set (perhaps in the constructor?)"; throw new IllegalStateException(msg); } // Calculate at what time a session would have been last accessed // for it to be expired at this point. In other words, subtract // from the current time the amount of time that a session can // be inactive before expiring. If the session was last accessed // before this time, it is expired. long expireTimeMillis = System.currentTimeMillis() - timeout; Date expireTime = new Date(expireTimeMillis); return lastAccessTime.before(expireTime); } else { if (log.isTraceEnabled()) { log.trace("No timeout for session with id [" + getId() + "]. Session is not considered expired."); } } return false;}
Try to detect from SecurityUtils. getSubject () step by step and feel how the session is set to subject.
After determining whether a Subject exists in the thread context, if it does not exist, we use the Subject internal class Builder for buildSubject ();
public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject;}
BuildSubject () delegates the Subject creation work to securityManager. createSubject (subjectContext)
CreateSubject calls resolveSession to process the session.
protected SubjectContext resolveSession(SubjectContext context) { if (context.resolveSession() != null) { log.debug("Context already contains a session. Returning."); return context; } try { //Context couldn't resolve it directly, let's see if we can since we have direct access to //the session manager: Session session = resolveContextSession(context); if (session != null) { context.setSession(session); } } catch (InvalidSessionException e) { log.debug("Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous " + "(session-less) Subject instance.", e); } return context;}
ResolveSession (subjectContext) first tries to get the session from the context (MapContext). If it cannot be obtained directly, change it to get the subject, and then call its getSession (false ).
If not, call resolveContextSession (subjectContext) to obtain the sessionId from MapContext.
Instantiate a SessionKey object based on the sessionId and obtain the session through the SessionKey instance.
The getSession (key) task is directly executed by sessionManager.
public Session getSession(SessionKey key) throws SessionException { return this.sessionManager.getSession(key);}
The sessionManager. getSession (key) method is defined in AbstractNativeSessionManager. This method is calledlookupSession(key)
,
LookupSession calldoGetSession(key)
,doGetSession(key)
Is a protected abstract, which is provided by the subclass AbstractValidatingSessionManager.
DoGetSession calls retrieveSession (key). This method attempts to obtain session information through sessionDAO.
Finally, verify the session if it is null (refer to SimpleSession. validate ()).
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException { enableSessionValidationIfNecessary(); log.trace("Attempting to retrieve session with key {}", key); Session s = retrieveSession(key); if (s != null) { validate(s, key); } return s;}
Session Listener
You can use the SessionListener interface or SessionListenerAdapter to listen to a session, and perform operations as needed when the session is created, stopped, or expired.
public interface SessionListener { void onStart(Session session); void onStop(Session session); void onExpiration(Session session);}
I only need to define a Listener and inject it into sessionManager.
package pac.testcase.shiro.listener;import org.apache.shiro.session.Session;import org.apache.shiro.session.SessionListener;public class MySessionListener implements SessionListener { public void onStart(Session session) { System.out.println(session.getId()+" start..."); } public void onStop(Session session) { System.out.println(session.getId()+" stop..."); } public void onExpiration(Session session) { System.out.println(session.getId()+" expired..."); }}
[main]realm0=pac.testcase.shiro.realm.MyRealm0realm1=pac.testcase.shiro.realm.MyRealm1authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategysessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager#sessionManager = org.apache.shiro.web.session.mgt.ServletContainerSessionManagersessionListener = pac.testcase.shiro.listener.MySessionListenersecurityManager.realms=$realm1securityManager.authenticator.authenticationStrategy = $authcStrategysecurityManager.sessionManager=$sessionManager#sessionManager.sessionListeners =$sessionListener securityManager.sessionManager.sessionListeners=$sessionListener
SessionDAO
SessionManager delegates the work of session CRUD To SessionDAO.
We can use a specific data source API to implement SessionDAO to store sessions in any data source.
public interface SessionDAO { Serializable create(Session session); Session readSession(Serializable sessionId) throws UnknownSessionException; void update(Session session) throws UnknownSessionException; void delete(Session session); Collection<Session> getActiveSessions();}
Of course, the subclass can also be used in the past.
- AbstractSessionDAO: verifies the session during create and read to ensure that the session is available and provides the method for generating the sessionId.
- CachingSessionDAO: Provides transparent cache support for session storage, and uses CacheManager to maintain the cache.
- EnterpriseCacheSessionDAO: uses an anonymous internal class to override createCache of AbstractCacheManager and returns a MapCache object.
- MemorySessionDAO: memory-based implementation. All sessions are stored in the memory.
The anonymous internal class in is the CacheManager of EnterpriseCacheSessionDAO.
MemorySessionDAO is used by default (note! DefaultWebSessionManager extends defasessessionmanager)
Of course, we can also try to use the cache.
Shiro does not enable EHCache by default, but it is recommended to enable EHCache to optimize session management to ensure that the session will not be lost at runtime.
Enabling EHCache for session persistence is very simple. First, we need to add a denpendency.
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro.version}</version></dependency>
Next, you only need to configure it. ini configuration as an example:
[main]realm0=pac.testcase.shiro.realm.MyRealm0realm1=pac.testcase.shiro.realm.MyRealm1authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategysessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManagercacheManager=org.apache.shiro.cache.ehcache.EhCacheManagersessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO#sessionManager = org.apache.shiro.web.session.mgt.ServletContainerSessionManagersessionListener = pac.testcase.shiro.listener.MySessionListenersecurityManager.realms=$realm1securityManager.authenticator.authenticationStrategy = $authcStrategysecurityManager.sessionManager=$sessionManagersessionManager.sessionListeners =$sessionListenersessionDAO.cacheManager=$cacheManager securityManager.sessionManager.sessionDAO=$sessionDAOsecurityManager.sessionManager.sessionListeners=$sessionListener
Definition and reference of cacheManager.
In addition, the sessionDAO used here is EnterpriseCacheSessionDAO.
As mentioned above, the CacheManager used by EnterpriseCacheSessionDAO is based on MapCache.
In fact, this setting will not affect, because EnterpriseCacheSessionDAO inherits CachingSessionDAO and CachingSessionDAO implements CacheManagerAware.
Note! The sessionDAO attribute is available only when SessionManager is used to implement classes.
(In fact, they defined sessionDAO in DefaultSessionManager, but there seems to be a plan to put sessionDAO in actactvalidatingsessionmanager .)
If you configure Shiro in the web application, you will be surprised to find that the sessionManager attribute of securityManger is actually ServletContainerSessionManager.
Take a look at the above hierarchy chart and we can see that ServletContainerSessionManager and DefaultSessionManager are irrelevant.
That is to say, ServletContainerSessionManager does not support SessionDAO (the cacheManger attribute is defined in CachingSessionDAO ).
In this case, the specified sessionManager is DefaultWebSessionManager.
For EhCache configuration, by default, EhCacheManager uses the specified configuration file, that is:
private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml";
Let's take a look at his Configuration:
<ehcache> <diskStore path="java.io.tmpdir/shiro-ehcache"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <cache name="shiro-activeSessionCache" maxElementsInMemory="10000" overflowToDisk="true" eternal="true" timeToLiveSeconds="0" timeToIdleSeconds="0" diskPersistent="true" diskExpiryThreadIntervalSeconds="600"/> <cache name="org.apache.shiro.realm.text.PropertiesRealm-0-accounts" maxElementsInMemory="1000" eternal="true" overflowToDisk="true"/></ehcache>
If you want to change the original settings, pay special attention to the following two attributes:
- OverflowToDisk = "true": ensures that the session will not be lost.
Eternal = "true": ensure that the session cache will not automatically expire. Setting it to false may be inconsistent with the session validation logic.
In addition, "shiro-activeSessionCache" is used for name by default"
Public static final String ACTIVESESSIONCACHE_NAME = "shiro-activeSessionCache ";
If you want to use another name, set activeSessionsCacheName in CachingSessionDAO or its subclass.
When a new session is created, the SessionDAO implementation class uses SessionIdGenerator to generate an ID for the session.
The default SessionIdGenerator is JavaUuidSessionIdGenerator. Its implementation is as follows:
public Serializable generateId(Session session) { return UUID.randomUUID().toString();}
Of course, you can also customize SessionIdGenerator.
Session Validation & Scheduling
For example, when a user uses a web application in a browser, the session is created and cached, but the user can directly turn off the browser, power off, power off, or other natural disasters when exiting.
Then the session status is unknown (it is orphaned ).
To prevent the garbage from being accumulated a little bit, we need to periodically check the session and delete the session if necessary.
So we have SessionValidationScheduler:
public interface SessionValidationScheduler { boolean isEnabled(); void enableSessionValidation(); void disableSessionValidation();}
Shiro only provides one implementation, ExecutorServiceSessionValidationScheduler. By default, the verification period is 60 minutes.
Of course, we can also change the verification period (in milliseconds) by modifying its interval attribute, for example:
sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationSchedulersessionValidationScheduler.interval = 3600000securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler
If you want to disable periodic verification sessions (for example, we have done some work outside Shiro), you can set
securityManager.sessionManager.sessionValidationSchedulerEnabled = false
If you do not want to delete invalid sessions (for example, we want to do some statistics), you can set
securityManager.sessionManager.deleteInvalidSessions = false