主要是web相关,javase不说
引入
SecurityManager是shiro的核心,负责与shiro的其他组件进行交互,而SessionManager是session的真正管理者,负责shiro的session管理。
在shrio中,当我们利用subject.login()
进行登录的时候或者是利用subject.getSession()的时候,就会创建一个session。这个session与servlet中的session是完全独立的,是shrio内部自己实现的一个session机制,但是器与servlet的session的理念基本相同。
利用subject登录的时候会创建session对象,不然怎么维持用户的登录状态呢?主动获取session的时候会创建session对象,这是肯定的嘛。
Shiro是如何维持Session的
在web中,我们默认使用的SecurityManager
是DefaultWebSecurityManager
,与之对应的默认使用的sessionManager就是ServletContainerSessionManager
。从他的名字我们就能看出来,其默认是以servlet容器来存储session,也就是说shrio默认情况下的session还是依赖servlet容器的。查看其源码我们能发现,ServletContainerSessionManager
管理的session,就是代理了HttpSession,HttpSession还是交给Servlet容器进行管理的。
DefaultWebSessionManager
这是shrio提供的另外一个session管理器,这个session管理器是完全独立的,不依赖于任何容器,在这个管理器的支持下,我们可以实现将session存储到redis。
HttpSession
中保存sessionId是通过Cookie,DefaultWebSessionManager
也是如此,这是一段DefaultWebSessionManager
中的代码
// 存储sessionId到cookie中
private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
if (currentId == null) {
String msg = "sessionId cannot be null when persisting for subsequent requests.";
throw new IllegalArgumentException(msg);
}
Cookie template = getSessionIdCookie();
Cookie cookie = new SimpleCookie(template);
String idString = currentId.toString();
cookie.setValue(idString);
// 将session保存到了response中
cookie.saveTo(request, response);
}
// 从cookie中读取sessionId
private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
if (!isSessionIdCookieEnabled()) {
log.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
return null;
}
if (!(request instanceof HttpServletRequest)) {
log.debug("Current request is not an HttpServletRequest - cannot get session ID cookie. Returning null.");
return null;
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
}
当我们在一次请求中第一次获取Subject的时候,其往往会解析出当前subject所关联的session,这样才能知道你是谁嘛,不过前提是之前存在session,否则还是等到我们手动获取session才进行创建。其解析就交给了我们的各种sessionManager,DefaultWebSessionManager
就是从cookie中进行解析的。
session保存在哪
ServletContainerSessionManager
因为利用的是Servlet容器中的HttpSession,所以就是由servlet容器保管,DefaultWebSessionManager
呢?这就要引出一个新的东西SessionDAO
DefaultWebSessionManager
是不管保存在哪个位置的,其是交给sessionDao来进行保存,通过sessionDao接口我们可以实现保存在任何地方,其中就包括redis
// 非常明显的增删改查
public interface SessionDAO {
Serializable create(Session session);
Session readSession(Serializable sessionId) throws UnknownSessionException;
void update(Session session) throws UnknownSessionException;
void delete(Session session);
// 返回活跃的session
Collection<Session> getActiveSessions();
}
DefaultWebSessionManager
中默认使用的sessionDao是MemorySessionDAO
,其利用一个ConcurrentMap来保存Session。
如果我们要实现一个自定义的SessionDao,一般是建议实现EnterpriseCacheSessionDAO
,因为其内部已经有了很多默认实现,我们需要修改的就比较少
Session过期
上面看起来一切都好,但是好像少了个重要的东西,那就是session的过期机制。一般的过期机制都是使用定时任务来进行处理的,Shrio也不例外。
AbstractValidatingSessionManager类中有如下方法,获取session时会经过此方法
@Override
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
// 启动session有效性的验证,也就是创建一个定时任务了
enableSessionValidationIfNecessary();
...
}
在具体往下追溯就可以追溯到一个类ExecutorServiceSessionValidationScheduler
,中间的一些小的东西就跳过了
这是其内部的run方法,想必已经相当的明显了
定时任务的间隔是1个小时
public void run() {
if (log.isDebugEnabled()) {
log.debug("Executing session validation...");
}
long startTime = System.currentTimeMillis();
// 调用sessionManager的效验方法
this.sessionManager.validateSessions();
long stopTime = System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
}
}
shiro的Session接口提供了一个touch方法,负责session的刷新。但是touch方法是什么时候被调用的呢?JavaSE需要我们自己定期的调用session的touch() 去更新最后访问时间;如果是Web应用,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间
AbstractShiroFilter类中
protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
if (!isHttpSessions()) { //'native' sessions
Subject subject = SecurityUtils.getSubject();
if (subject != null) {
Session session = subject.getSession(false);
if (session != null) {
try {
session.touch();
} catch (Throwable t) {
log.error("session.touch() method invocation has failed. Unable to update" +
"the corresponding session's last access time based on the incoming request.", t);
}
}
}
}
}