Shiro权限管理框架(四):深入分析Shiro中的Session管理
其实关于Shiro的一些学习笔记很早就该写了,因为懒癌和拖延症晚期一直没有落实,直到今天公司的一个项目碰到了在集群环境的单点登录频繁掉线的问题,为了解决这个问题,Shiro相关的文档和教程没少翻。最后问题解决了,但我觉得我也是时候来做一波Shiro学习笔记了。
本篇是Shiro系列第四篇,Shiro中的过滤器初始化流程和实现原理。Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看源码追寻Shiro中的过滤器的实现原理。
首发地址:https://www.guitu18.com/post/2019/08/08/45.html
Session

SessionManager

我们在配置Shiro时配置了一个DefaultWebSecurityManager,先来看下

DefaultWebSecurityManager
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
在它的构造方法中注入了一个ServletContainerSessionManager
public class ServletContainerSessionManager implements WebSessionManager {
public Session getSession(SessionKey key) throws SessionException {
if (!WebUtils.isHttp(key)) {
String msg = "SessionKey must be an HTTP compatible implementation.";
throw new IllegalArgumentException(msg);
}
HttpServletRequest request = WebUtils.getHttpRequest(key);
Session session = null;
HttpSession httpSession = request.getSession(false);
if (httpSession != null) {
session = createSession(httpSession, request.getRemoteHost());
}
return session;
}
private String getHost(SessionContext context) {
String host = context.getHost();
if (host == null) {
ServletRequest request = WebUtils.getRequest(context);
if (request != null) {
host = request.getRemoteHost();
}
}
return host;
}
protected Session createSession(SessionContext sessionContext) throws AuthorizationException {
if (!WebUtils.isHttp(sessionContext)) {
String msg = "SessionContext must be an HTTP compatible implementation.";
throw new IllegalArgumentException(msg);
}
HttpServletRequest request = WebUtils.getHttpRequest(sessionContext);
HttpSession httpSession = request.getSession();
String host = getHost(sessionContext);
return createSession(httpSession, host);
}
protected Session createSession(HttpSession httpSession, String host) {
return new HttpServletSession(httpSession, host);
}
}
ServletContainerSessionManager本身并不管理会话,它最终操作的还是HttpSession,所以只能在Servlet容器中起作用,它不能支持除使用HTTP协议的之外的任何会话。
所以一般我们配置Shiro都会配置一个DefaultWebSessionManager,它继承了DefaultSessionManager,看看DefaultSessionManager的构造方法:
public DefaultSessionManager() {
this.deleteInvalidSessions = true;
this.sessionFactory = new SimpleSessionFactory();
this.sessionDAO = new MemorySessionDAO();
}
这里的sessionDAO初始化了一个MemorySessionDAO,它其实就是一个Map,在内存中通过键值对管理Session。
public MemorySessionDAO() {
this.sessions = new ConcurrentHashMap<Serializable, Session>();
}
HttpServletSession
public class HttpServletSession implements Session {
public HttpServletSession(HttpSession httpSession, String host) {
if (httpSession == null) {
String msg = "HttpSession constructor argument cannot be null.";
throw new IllegalArgumentException(msg);
}
if (httpSession instanceof ShiroHttpSession) {
String msg = "HttpSession constructor argument cannot be an instance of ShiroHttpSession. This " +
"is enforced to prevent circular dependencies and infinite loops.";
throw new IllegalArgumentException(msg);
}
this.httpSession = httpSession;
if (StringUtils.hasText(host)) {
setHost(host);
}
}
protected void setHost(String host) {
setAttribute(HOST_SESSION_KEY, host);
}
public void setAttribute(Object key, Object value) throws InvalidSessionException {
try {
httpSession.setAttribute(assertString(key), value);
} catch (Exception e) {
throw new InvalidSessionException(e);
}
}
}
Shiro的HttpServletSession只是对javax.servlet.http.HttpSession进行了简单的封装,所以在Web应用中对Session的相关操作最终都是对javax.servlet.http.HttpSession进行的,比如上面代码中的setHost()是将内容以键值对的形式保存在httpSession中。
先来看下这张图:

先了解上面这几个类的关系和作用,然后我们想管理Shiro中的一些数据就非常方便了。
SessionDao是Session管理的顶层接口,定义了Session的增删改查相关方法。
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();
}
AbstractSessionDao是一个抽象类,在它的构造方法中定义了JavaUuidSessionIdGenerator作为SessionIdGenerator用于生成SessionId。它虽然实现了create()和readSession()两个方法,但具体的流程调用的是它的两个抽象方法doCreate()和doReadSession(),需要它的子类去干活。
public abstract class AbstractSessionDAO implements SessionDAO {
private SessionIdGenerator sessionIdGenerator;
public AbstractSessionDAO() {
this.sessionIdGenerator = new JavaUuidSessionIdGenerator();
}
public Serializable create(Session session) {
Serializable sessionId = doCreate(session);
verifySessionId(sessionId);
return sessionId;
}
protected abstract Serializable doCreate(Session session);
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = doReadSession(sessionId);
if (s == null) {
throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
}
return s;
}
protected abstract Session doReadSession(Serializable sessionId);
}
看上面那张类图AbstractSessionDao的子类有三个,查看源码发现CachingSessionDAO是一个抽象类,它并没有实现这两个方法。在它的子类EnterpriseCacheSessionDAO中实现了doCreate()和doReadSession(),但doReadSession()是一个空实现直接返回null。
public EnterpriseCacheSessionDAO() {
setCacheManager(new AbstractCacheManager() {
@Override
protected Cache<Serializable, Session> createCache(String name) throws CacheException {
return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
}
});
}
EnterpriseCacheSessionDAO依赖于它的父级CachingSessionDAO,在他的构造方法中向父类注入了一个AbstractCacheManager的匿名实现,它是一个基于内存的SessionDao,它所创建的MapCache就是一个Map。
我们在Shiro配置类里通过 sessionManager.setSessionDAO(new EnterpriseCacheSessionDAO()); 来使用它。然后在CachingSessionDAO.getCachedSession() 打个断点测试一下,可以看到cache就是一个ConcurrentHashMap,在内存中以Key-Value的形式保存着JSESSIONID和Session的映射关系。

再来看AbstractSessionDao的第三个实现MemorySessionDAO,它就是一个基于内存的SessionDao,简单直接,构造方法直接new了一个ConcurrentHashMap。
public MemorySessionDAO() {
this.sessions = new ConcurrentHashMap<Serializable, Session>();
}
那么它和EnterpriseCacheSessionDAO有啥区别,其实EnterpriseCacheSessionDAO只是CachingSessionDAO的一个默认实现,在CachingSessionDAO中cacheManager是没有默认值的,在EnterpriseCacheSessionDAO的构造方法将其初始化为一个ConcurrentHashMap。
如果我们直接用EnterpriseCacheSessionDAO其实和MemorySessionDAO其实没有什么区别,都是基于Map的内存型SessionDao。而CachingSessionDAO的目的是为了方便扩展的,用户可以继承CachingSessionDAO并注入自己的Cache实现,比如以Redis缓存Session的RedisCache。
其实在业务上如果需要Redis来管理Session,那么直接继承AbstractSessionDao更好,有Redis支撑中间的Cache就是多余的,这样还可以做分布式或集群环境的Session共享(分布式或集群环境如果中间还有一层Cache那么还要考虑同步问题,所以集群环境千万不要继承EnterpriseCacheSessionDAO来实现Session共享)。
回到开篇提到的我们公司项目集群环境下单点登录频繁掉线的问题,其实就是中间那层Cache造成的。我们的业务代码中RedisSessionDao是继承自EnterpriseCacheSessionDAO的,这样一来那在Redis之上还有一个基于内存的Cache层。此时用户的Session如果发生变更,虽然Redis中的Session是同步的,Cache层没有同步,导致的现象就是用户在一台服务器的Session是有效的,另一台服务器Cache中的Session还是旧的,然后用户就被迫下线了。
最后,关于Shiro中Session管理(持久化/共享)的一些总结:
如果只是在单机环境下需要做Session持久化(服务器重启保持Session在线),那么最好继承EnterpriseCacheSessionDAO来增加本地Session缓存以减少I/O开销,否则大量的
doReadSession()调用会造成I/O甚至网络压力(单机环境下能省一点是一点嘛,集群就没办法省了);如果是在集群环境下做Session共享,千万不要继承EnterpriseCacheSessionDAO,会产生服务器间的本地Session缓存不同步问题,直接继承AbstractSessionDAO即可;
关于集群环境下Session共享详细配置可以看这篇:Shiro权限管理框架(二):Shiro结合Redis实现分布式或集群环境下的Session共享
Shiro权限管理框架(四):深入分析Shiro中的Session管理的更多相关文章
- Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理
本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...
- Shiro权限管理框架(一):Shiro的基本使用
首发地址:https://www.guitu18.com/post/2019/07/26/43.html 核心概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码 ...
- Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享
首发地址:https://www.guitu18.com/post/2019/07/28/44.html 本篇是Shiro系列第二篇,使用Shiro基于Redis实现分布式环境下的Session共享. ...
- Shiro权限管理框架(五):自定义Filter实现及其问题排查记录
明确需求 在使用Shiro的时候,鉴权失败一般都是返回一个错误页或者登录页给前端,特别是后台系统,这种模式用的特别多.但是现在的项目越来越多的趋向于使用前后端分离的方式开发,这时候就需要响应Json数 ...
- Shiro权限管理框架
一.Shiro介绍 Apache Shiro 是Java 的一个安全框架.Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境.Shiro 可以 ...
- Shiro权限管理框架详解
1 权限管理1.1 什么是权限管理 基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被 ...
- shiro权限管理框架与springmvc整合
shiro是apache下的一个项目,和spring security类似,用于用户权限的管理‘ 但从易用性和学习成本上考虑,shiro更具优势,同时shiro支持和很多接口集成 用户及权限管理是众多 ...
- Apache Shiro权限框架在SpringMVC+Hibernate中的应用
在做网站开发中,用户权限必须要考虑的,权限这个东西很重要,它规定了用户在使用中能进行哪 些操作,和不能进行哪些操作:我们完全可以使用过滤器来进行权限的操作,但是有了权限框架之后,使用起来会非常的方便, ...
- Shiro 权限管理框架
一.什么是Shiro Apache Shiro是一个强大易用的java安全框架,提供认证.授权.加密和会话管理等功能 · 认证:用户身份识别,俗称“登录”: · 授权:访问控制 · 密码加密:保护或隐 ...
随机推荐
- Introduction to ES6上课笔记
课程链接:https://scrimba.com/g/gintrotoes6 这个网站有几个热门的前端技术栈的免费课程,上课体验除了英语渣只能看代码理解听老师讲的一知半解之外,是极佳的学编程的网站了. ...
- Intellij IDEA 2019 + Java Spring MVC + Hibernate学习笔记(2)
书接上文 首先根据各种Spring MVC教程,建立了基础的结构,是否合理不知道,姑且先这样,有问题再解决问题.学习新东西,不能怕掉坑里... 查询网上别人的经历说需要把根目录下的lib目录下的所有包 ...
- 基于操作系统原理的Linux 系统的安装
一.实验目的 1.了解Linux操作系统的发行版本. 2.掌握Red Hat Linux 9.0的安装方法. (可用Red Hat Linux 5.0版本替代9.0版本) 3.了解Linux其他版本( ...
- mysql 排序规则
一.对比 1.utf8_general_ci 不区分大小写,utf8_general_cs 区分大小写 2.utf8_bin: compare strings by the binary value ...
- 【Python】机器学习之单变量线性回归练习(计算Cost Function)
注:练习来自于吴恩达机器学习 翻译后的题目: 你是一个餐厅的老板,你想在其他城市开分店,所以你得到了一些数据(数据在本文最下方),数据中包括不同的城市人口数和该城市带来的利润.第一列是城市的人口数,第 ...
- 推荐几个我近期排查线上http接口偶发415时用到的工具
导读:近期有一个业务部门的同学反馈说他负责的C工程在小概率情况下SpringMvc会返回415,通过输出的日志可以确定是SpringMvc找不到content-type这个头了,具体为什么找不到了呢? ...
- (三)分布式数据库tidb-隔离级别详解
tidb隔离级别详解: 1.TiDB 支持的隔离级别是 Snapshot Isolation(SI),它和 Repeatable Read(RR) 隔离级别基本等价,详细情况如下: ● TiDB 的 ...
- Java基础学习笔记(四) - 认识final关键字、权限修饰符和内部类
一.final关键字 为什么要使用 final 关键字? 通过继承我们知道,子类可以重写父类的成员变量和方法.final 关键字可以用于修饰父类,父类成员变量和方法,使其内容不可以被更改. 1.被修饰 ...
- ELK 学习笔记之 Logstash之codec配置
Logstash之codec: Logstash处理流程: input=>decode=>filter=>encode=>output 分类: Plain编码: input{ ...
- golang grpc 详解
https://segmentfault.com/a/1190000007880647