上一篇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

如有问题,欢迎评论回复!

Shiro源码解析-Session篇的更多相关文章

  1. Shiro源码解析-登录篇

    本文以循序渐进的方式解析Shiro整个login过程的处理,读者可以边阅读本文边自己看代码来思考体会,如有问题,欢迎评论区探讨! 笔者shiro的demo源码路径:https://github.com ...

  2. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  3. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  4. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  5. jQuery2.x源码解析(回调篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...

  6. myBatis源码解析-类型转换篇(5)

    前言 开始分析Type包前,说明下使用场景.数据构建语句使用PreparedStatement,需要输入的是jdbc类型,但我们一般写的是java类型.同理,数据库结果集返回的是jdbc类型,而我们需 ...

  7. Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  8. myBatis源码解析-数据源篇(3)

    前言:我们使用mybatis时,关于数据源的配置多使用如c3p0,druid等第三方的数据源.其实mybatis内置了数据源的实现,提供了连接数据库,池的功能.在分析了缓存和日志包的源码后,接下来分析 ...

  9. myBatis源码解析-反射篇(4)

    前沿 前文分析了mybatis的日志包,缓存包,数据源包.源码实在有点难顶,在分析反射包时,花费了较多时间.废话不多说,开始源码之路. 反射包feflection在mybatis路径如下: 源码解析 ...

随机推荐

  1. java String,StringBuffer,StringBuilder区别及联系

    String 字符串常量(不可变)StringBuffer 字符串变量(线程安全,可变) StringBuilder 字符串变量(非线程安全,性能优,可变) 简要的说, String 类型和 Stri ...

  2. Android Messenger

    说到Android进程间通信,大家肯定能想到的是编写aidl文件,然后通过aapt生成的类方便的完成服务端,以及客户端代码的编写.如果你对这个过程不熟悉,可以查看Android aidl Binder ...

  3. scala学习(idea编译过程https://blog.csdn.net/guiying712/article/details/68947747)

    scala官网 https://www.scala-lang.org/ 菜鸟教程学习 http://www.runoob.com/scala/scala-basic-syntax.html w3sch ...

  4. U盘安装Centos6.2

    原文地址:http://www.dedecms.com/knowledge/servers/linux-bsd/2012/0819/8452.html. 第一步:制作系统U盘(略). 第二步:设置BI ...

  5. CSS选择器与jQuery选择器的异同:一些特殊的选择器

    在CSS3选择器标淮草案定义的选择器语法中,jQuery支持相当完整的一套子集,同时还添加了一些非标准但很有用的伪类.下面是一些jQuery扩展的选择器:(来自http://www.cnblogs.c ...

  6. Linq转换操作之OfType,Cast,AsEnumerable,ToLookup源码分析

    Linq转换操作之OfType,Cast,AsEnumerable,ToLookup源码分析 一:Tolookup 1. 从方法的注解上可以看到,ToLookup也是一个k,v的形式,那么问题来了,它 ...

  7. log4net 入门使用

    log4net 是dotnet平台下的一个日记记录组件. 一  NuGet中安装log4net包: 二 配置log4net.config文件 配置文件内容: <?xml version=&quo ...

  8. 提高SQL Server数据库效率常用方法

    1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2.I/O吞吐量小,形成了瓶颈效应. 3.没有创建计算列导致查询不优化. 4.内存不足 5.网络速度慢 6.查询出的数据量过大 ...

  9. System.Data.OracleClient.dll方式操作oracle数据库

    System.Data.OracleClient.dll方式操作oracle数据库 一.查询语句: using (OracleConnection conn = new OracleConnectio ...

  10. 手把手带你打造一个 Android 热修复框架

    本文来自网易云社区 作者:王晨彦 Application 处理 上面我们已经对所有 class 文件插入了 Hack 的引用,而插入 dex 是在 Application 中,Application ...