欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

程序员文章站 2022-07-02 14:10:00
前言 开心一刻 老公酷爱网络游戏,老婆无奈,只得告诫他:你玩就玩了,但是千万不可以在游戏里找老婆,不然,哼哼。。。 老公嘴角露出了微笑:放心吧亲爱的,我绝对不会在游戏里找老婆的!因为我有老公! 老婆:...... 路漫漫其修远兮,吾将上下而求索! github:https://github.com/ ......

前言

  开心一刻  

    老公酷爱网络游戏,老婆无奈,只得告诫他:你玩就玩了,但是千万不可以在游戏里找老婆,不然,哼哼。。。
    老公嘴角露出了微笑:放心吧亲爱的,我绝对不会在游戏里找老婆的!因为我有老公!
    老婆:......

  路漫漫其修远兮,吾将上下而求索!

  github:

  码云(gitee):

前情回顾

  大家还记得讲了什么吗,我们来一起简单回顾下:

    securitymanager是shiro的核心,负责与shiro的其他组件进行交互;sessionmanager是session的真正管理者,负责shiro的session管理;

    sessionssecuritymanager的start方法中将session的创建委托给了具体的sessionmanager,是创建session的关键入口。

    simplesession是shiro完完全全的自己实现,是shiro对session的一种拓展;实现了validatingsession接口,具有自我校验的功能;一般不对外暴露,暴露的往往是他的代理:delegatingsession;simplesession有几个属性值得重点关注下,如下

        id:就是session id;

        starttimestamp:session的创建时间;

        stoptimestamp:session的失效时间;

        lastaccesstime:session的最近一次访问时间,初始值是starttimestamp

        timeout:session的有效时长,默认30分钟

        expired:session是否到期

        attributes:session的属性容器

查询

  session的创建完成后,会将session(simplesession类型)对象的代理对象(delegatingsession)装饰成stoppingawareproxiedsession对象,然后绑定到subject(类型是delegatingsubject);

  session session = subject.getsession();返回的就是绑定在当前subjuct的session。注意subject的实际类型是:delegatingsubject,如下图

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

刷新

  shiro的session接口提供了一个touch方法,负责session的刷新;session的代理对象最终会调用simplesession的touch():

public void touch() {
    this.lastaccesstime = new date();        // 更新最后被访问时间为当前时间
}

  但是touch方法是什么时候被调用的呢?javase需要我们自己定期的调用session的touch() 去更新最后访问时间;如果是web应用,每次进入shirofilter都会自动调用session.touch()来更新最后访问时间,shirofilter的类图如下:

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

  shirofilter自动调用session.touch()如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

过期

  如果是让我们自己实现session过期的判断,我们会怎么做了?我们来看看shiro是怎么做的,或许我们能够从中学到一些经验。

  启动校验定时任务

    还记得abstractvalidatingsessionmanager中createsession方法吗?在调用docreatesession方法之前调用enablesessionvalidationifnecessary(),enablesessionvalidationifnecessary代码如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
private void enablesessionvalidationifnecessary() {
    // 获取session验证调度器
    sessionvalidationscheduler scheduler = getsessionvalidationscheduler();
    // session验证调度器开启 && (调度器为空或调度器不可用)
    if (issessionvalidationschedulerenabled() && (scheduler == null || !scheduler.isenabled())) {
        enablesessionvalidation();        // 开启session验证
    }
}
view code

    第一次创建session的时候,如果session验证调度器启用(默认是启用),那么调用enablesessionvalidation(),enablesessionvalidation代码如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
protected synchronized void enablesessionvalidation() {
    sessionvalidationscheduler scheduler = getsessionvalidationscheduler();        // 获取调取器
    if (scheduler == null) {
        scheduler = createsessionvalidationscheduler();                            // 创建调取器,实际类型是executorservicesessionvalidationscheduler
        setsessionvalidationscheduler(scheduler);                                // 将调度器绑定到sessionmanager
    }
    // it is possible that that a scheduler was already created and set via 'setsessionvalidationscheduler()'
    // but would not have been enabled/started yet
    if (!scheduler.isenabled()) {
        if (log.isinfoenabled()) {
            log.info("enabling session validation scheduler...");
        }
        scheduler.enablesessionvalidation();                                    // 启动定时任务,验证session
        aftersessionvalidationenabled();                                        // 什么也没做,供继承,便于拓展
    }
}
view code

    executorservicesessionvalidationscheduler类图如下,它实现了runnable接口
shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有

  调用scheduler的enablesessionvalidation(),enablesessionvalidation方法如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
public void enablesessionvalidation() {
    if (this.interval > 0l) {
        // 创建scheduledexecutorservice
        this.service = executors.newsinglethreadscheduledexecutor(new threadfactory() {  
            private final atomicinteger count = new atomicinteger(1);

            public thread newthread(runnable r) {  
                thread thread = new thread(r);  
                thread.setdaemon(true);  
                thread.setname(threadnameprefix + count.getandincrement());
                return thread;  
            }  
        });
        //  初始化service interval时长之后开始执行this的run方法,每隔interval执行一次;注意interval的单位是timeunit.milliseconds
        this.service.scheduleatfixedrate(this, interval, interval, timeunit.milliseconds);    // this就是executorservicesessionvalidationscheduler自己
    }
    this.enabled = true;
}
view code

  session校验 

  定时(默认每隔60分钟)的调用executorservicesessionvalidationscheduler的run方法,run方法中调用sessionmanager的validatesessions方法来完成session的验证,validatesessions方法如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
/**
 * @see validatingsessionmanager#validatesessions()
 */
public void validatesessions() {
    if (log.isinfoenabled()) {
        log.info("validating all active sessions...");
    }

    int invalidcount = 0;

    // 从sessiondao中获取全部的session
    // sessiondao可以是默认的memorysessiondao,也可以是我们定制的cachingsessiondao
    collection<session> activesessions = getactivesessions();

    if (activesessions != null && !activesessions.isempty()) {
        // 一个一个校验
        for (session s : activesessions) {
            try {
                //simulate a lookup key to satisfy the method signature.
                //this could probably stand to be cleaned up in future versions:
                sessionkey key = new defaultsessionkey(s.getid());
                validate(s, key);        // 真正校验的方法
            } catch (invalidsessionexception e) {
                if (log.isdebugenabled()) {
                    boolean expired = (e instanceof expiredsessionexception);
                    string msg = "invalidated session with id [" + s.getid() + "]" +
                            (expired ? " (expired)" : " (stopped)");
                    log.debug(msg);
                }
                invalidcount++;            // 统计上次到这次定时任务间隔内过期的session个数
            }
        }
    }

    if (log.isinfoenabled()) {
        string msg = "finished session validation.";
        if (invalidcount > 0) {
            msg += "  [" + invalidcount + "] sessions were stopped.";
        } else {
            msg += "  no sessions were stopped.";
        }
        log.info(msg);
    }
}
view code

  validate方法如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
protected void validate(session session, sessionkey key) throws invalidsessionexception {
    try {
        dovalidate(session);                    // 真正校验session
    } catch (expiredsessionexception ese) {
        onexpiration(session, ese, key);        // 从sessiondao中删除过期的session
        throw ese;                                // 抛出异常供上层统计用
    } catch (invalidsessionexception ise) {
        oninvalidation(session, ise, key);        // 从sessiondao中删除不合法的session
        throw ise;                                // 抛出异常供上层统计用
    }
}
view code

  通过捕获dovalidate()抛出的异常来剔除过期的或不合法的session,并将异常接着往上抛,供上层统计过期数量。注意:expiredsessionexception的父类是stoppedsessionexception,而stoppedsessionexception的父类是invalidsessionexception。

  dovalidate方法如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
protected void dovalidate(session session) throws invalidsessionexception {
    if (session instanceof validatingsession) {
        ((validatingsession) session).validate();        // 校验session是否过期
    } else {                                            // 若session不是validatingsession类型,则抛出illegalstateexception异常
        string msg = "the " + getclass().getname() + " implementation only supports validating " +
                "session implementations of the " + validatingsession.class.getname() + " interface.  " +
                "please either implement this interface in your session implementation or override the " +
                abstractvalidatingsessionmanager.class.getname() + ".dovalidate(session) method to perform validation.";
        throw new illegalstateexception(msg);
    }
}
view code

    若session不是validatingsession类型,则抛出illegalstateexception异常

  validate方法如下

shiro源码篇 - shiro的session的查询、刷新、过期与删除,你值得拥有
public void validate() throws invalidsessionexception {
    //check for stopped:
    if (isstopped()) {                    
    // sesson已经停止了,则抛出stoppedsessionexception;理论上来讲不会出现这种情况,但程序的事没有100%保障
        //timestamp is set, so the session is considered stopped:
        string msg = "session with id [" + getid() + "] has been " +
                "explicitly stopped.  no further interaction under this session is " +
                "allowed.";
        throw new stoppedsessionexception(msg);
    }

    //check for expiration
    if (istimedout()) {     // 校验是否过期,校验方法是:lastaccesstime是否小于(当前时间 - session有效时长)
        expire();            // 更新session的stoptimestamp为当前时间,session的expired为true

        //throw an exception explaining details of why it expired:
        date lastaccesstime = getlastaccesstime();
        long timeout = gettimeout();

        serializable sessionid = getid();

        dateformat df = dateformat.getinstance();
        string msg = "session with id [" + sessionid + "] has expired. " +
                "last access time: " + df.format(lastaccesstime) +
                ".  current time: " + df.format(new date()) +
                ".  session timeout is set to " + timeout / millis_per_second + " seconds (" +
                timeout / millis_per_minute + " minutes)";
        if (log.istraceenabled()) {
            log.trace(msg);
        }
        throw new expiredsessionexception(msg);    // 抛出expiredsessionexception供上层使用
    }
}
view code

  校验总结

    1、sesion的有效时长默认30分钟;定时任务默认是每60分钟执行一次,第一次执行是在定时器初始化完成60分钟后执行;

    2、session不是validatingsession类型,则抛出illegalstateexception异常;session已经停止了则抛出stoppedsessionexception;session过期则抛出expiredsessionexception异常;理论上来讲illegalstateexception与stoppedsessionexception不会被抛出,应该全是expiredsessionexception异常;expiredsessionexception继承自stoppedsessionexception,而stoppedsessionexception又继承自illegalstateexception;

    3、校验session的时候,抛出了异常,将其捕获,从sessiondao中删除对应的session,并使过期数量自增1

删除

  夹杂在过期定时任务中,与过期是同时进行的,利用的异常机制;当然session操作的时候sessionmanager也有session的校验,伴随着就有session的删除。

疑问

  定时任务默认每60分钟执行一次,而session有效时长默认是30分钟,那么定时任务执行的间隔内肯定有session过期了,而我们在这个间隔内操作了过期的session怎么办

  其实这个问题应该这么来问:在定时任务间隔期间,对session的操作有没有做校验处理?答案是肯定的。

  通过上面的讲解我们知道:session的操作通过代理之后,都会来到sessionmanager,sessionmanager通过处理之后再到simplesession;abstractnativesessionmanager中将session操作放给simplesession之前,都会调用lookupsession方法,跟进lookupsession你会发现,里面也有session的校验。

  所以session的校验,不只是定制任务在执行,很多session的操作都有做session的校验。

总结

  1、一般我们操作subject是delegatingsubject类型,delegatingsubject中将subject的操作委托给了securitymanager;一般操作的session是session的代理,代理将session操作委托给sessionmanager,sesionmanager校验之后再转交给simplesession;

  2、session过期定时任务默认60分钟执行一次,所session已过期或不合法,则抛出对应的异常,上层通过捕获异常从sessiondao中删除session

  3、不只定时任务做session的校验,session的基本操作都在sessionmanager中有做session的校验

参考

  《跟我学shiro》