Shiro源码解析-Session篇
上一篇Shiro源码解析-登录篇中提到了在登录验证成功后有对session的处理,但未详细分析,本文对此部分源码详细分析下。
1. 分析切入点:DefaultSecurityManger的login方法
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = authenticate(token); } catch (AuthenticationException ae) { try { onFailedLogin(token, ae, subject); } catch (Exception e) { if (log.isInfoEnabled()) { log.info("onFailedLogin method threw an " + "exception. Logging and propagating original AuthenticationException.", e); } } throw ae; //propagate } Subject loggedIn = createSubject(token, info, subject); // 登录用户验证成功之后进行session处理 onSuccessfulLogin(token, info, loggedIn); return loggedIn; }
继续DefaultSecurityManger
protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) { SubjectContext context = createSubjectContext(); context.setAuthenticated(true); context.setAuthenticationToken(token); context.setAuthenticationInfo(info); if (existing != null) { context.setSubject(existing); } return createSubject(context); // 此处创建Subject }
DefaultSecurityManger的createSubject方法
public Subject createSubject(SubjectContext subjectContext) { //create a copy so we don't modify the argument's backing map: SubjectContext context = copy(subjectContext); //ensure that the context has a SecurityManager instance, and if not, add one: context = ensureSecurityManager(context); //Resolve an associated Session (usually based on a referenced session ID), and place it in the context before //sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the //process is often environment specific - better to shield the SF from these details: context = resolveSession(context); //Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first //if possible before handing off to the SubjectFactory: context = resolvePrincipals(context); Subject subject = doCreateSubject(context); //save this subject for future reference if necessary: //(this is needed here in case rememberMe principals were resolved and they need to be stored in the //session, so we don't constantly rehydrate the rememberMe PrincipalCollection on every operation). //Added in 1.2: save(subject); // 在这一步存储session return subject; }
protected void save(Subject subject) {
this.subjectDAO.save(subject);
}
2. 转移到DefaultSubjectDAO
调用DefaultSubjectDAO.save(subject)方法
public Subject save(Subject subject) { if (isSessionStorageEnabled(subject)) { // 默认sessionStorage是enabled saveToSession(subject); // 看这里 } else { log.trace("Session storage of subject state for Subject [{}] has been disabled: identity and " + "authentication state are expected to be initialized on every request or invocation.", subject); } return subject; }
调用DefaultSubjectDAO.saveToSession(subject)方法
protected void saveToSession(Subject subject) { //performs merge logic, only updating the Subject's session if it does not match the current state: mergePrincipals(subject); mergeAuthenticationState(subject); }
调用DefaultSubjectDAO的mergePrincipals方法
protected void mergePrincipals(Subject subject) { //merge PrincipalCollection state: PrincipalCollection currentPrincipals = null; //SHIRO-380: added if/else block - need to retain original (source) principals //This technique (reflection) is only temporary - a proper long term solution needs to be found, //but this technique allowed an immediate fix that is API point-version forwards and backwards compatible // //A more comprehensive review / cleaning of runAs should be performed for Shiro 1.3 / 2.0 + if (subject.isRunAs() && subject instanceof DelegatingSubject) { try { Field field = DelegatingSubject.class.getDeclaredField("principals"); field.setAccessible(true); currentPrincipals = (PrincipalCollection)field.get(subject); } catch (Exception e) { throw new IllegalStateException("Unable to access DelegatingSubject principals property.", e); } } if (currentPrincipals == null || currentPrincipals.isEmpty()) { currentPrincipals = subject.getPrincipals(); } Session session = subject.getSession(false); // 取得session,如果不存在,并不会主动创建session if (session == null) { if (!isEmpty(currentPrincipals)) { session = subject.getSession(); // 第一次用户访问时,会创建session,那么session是如何创建的呢?在缓存还是在DB中,请继续往下看 session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals); } // otherwise no session and no principals - nothing to save } else { PrincipalCollection existingPrincipals = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); if (isEmpty(currentPrincipals)) { if (!isEmpty(existingPrincipals)) { session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); } // otherwise both are null or empty - no need to update the session } else { if (!currentPrincipals.equals(existingPrincipals)) { session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals); } // otherwise they're the same - no need to update the session } } }
3. 转移到DelegatingSubject
调用DelegatingSubject的getSession
public Session getSession() { return getSession(true); // 参数为true,表示不存在此session时创建 } 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); // session创建,从这里再往下走(这里的securityMananger是SessionSecurityManager) this.session = decorate(session); } return this.session; }
4. 转移到SessionSecurityManager
public Session start(SessionContext context) throws AuthorizationException { return this.sessionManager.start(context); // 如果没有设置sessionManager,则调用默认的ServletContainerSessionManager(内存存储session),demo代码使用如下 }
5. 如果像demo中soure一样,设置了的sessionManager为DefaultWebSessionManager,那么接下来会转移为到它的父类AbstractValidatingSessionManager
protected Session createSession(SessionContext context) throws AuthorizationException { enableSessionValidationIfNecessary(); return doCreateSession(context); } protected Session doCreateSession(SessionContext context) { Session s = newSessionInstance(context); if (log.isTraceEnabled()) { log.trace("Creating session for host {}", s.getHost()); } create(s); // 在此处创建Session return s; } protected Session newSessionInstance(SessionContext context) { return getSessionFactory().createSession(context); }
6. 调用DefaultSessionManager
protected void create(Session session) { if (log.isDebugEnabled()) { log.debug("Creating new EIS record for new session instance [" + session + "]"); } sessionDAO.create(session); // 到这里大家应该看到了,你配置的SessionDAO在什么时候调用,demo中使用EnterpriseCacheSessionDAO }
7. 先调用的父类CachingSessionDAO的create方法
public Serializable create(Session session) { Serializable sessionId = super.create(session);// 这里只是生成sessionId,至于生成使用的算法可以在config中设置sessionIdGenerator cache(session, sessionId); // 生成session并存储到cache,过程在下面 return sessionId; }
8. 接下来cache方法
protected void cache(Session session, Serializable sessionId) { if (session == null || sessionId == null) { return; } Cache<Serializable, Session> cache = getActiveSessionsCacheLazy(); // 创建cache,名字默认为shiro-activeSessionCache if (cache == null) { return; } cache(session, sessionId, cache); // 存储到cache }
protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
cache.put(sessionId, session); // 这里调用的Ehcache的put方法,最终是存储在cache中(当然,如果你自定义了SessionDAO,那就可以存储在你指定的地方)
}
到这里大家基本都明白了整个过程吧,通过源码分析我们可以明白以下关键点
- SessionManager什么时候调用
- SessionDAO何时调用
- SessionId什么时候生成
- Session什么时候存储到Cache
如有问题,欢迎评论回复!
---栖息之鹰(一个外表懒洋洋的内心有激情的程序员)
此博客为笔者原著,转载时请注明出处,谢谢!