1、Shiro默认的Session处理方式
<!-- 定义 Shiro 主要业务对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- <property name="sessionManager" ref="sessionManager" /> -->
<property name="realm" ref="systemAuthorizingRealm" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
这里从DefaultWebSecurityManager这里看起,这个代码是定义的Shiro安全管理对象,看下面的构造方法(代码 1-1)
(代码 1-1)
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
从 构造方法里面可以看出,这里面有 publicvoid setRememberMeManager(RememberMeManager rememberMeManager) 和 publicvoid setSessionManager(SessionManager sessionManager)两个方法。这两个分别是对Shiro的remembereMe功能和Session功能的管理。其中在构造方法里面可以看到,其实一开是就设置了SessionManager,就是默认的:ServletContainerSessionManager().接下来看看默认的ServletContainerSessionManager()是怎么玩转Session的.看下图。这个图显示了这个类的这些个方法
这个类通过getSession(SessionKey)获得Sesison,下面看看这个方法干了些什么(代码1-2)
(代码1-2)
publicSession getSession(SessionKey key) throws SessionException {
if (!WebUtils.isHttp(key)) { //判断是不是http的key,否则抛异常
String msg = "SessionKey must be an HTTP compatible implementation.";
throw new IllegalArgumentException(msg);
}
HttpServletRequest request = WebUtils.getHttpRequest(key); //通过工具类获得HttpServletRequest 这类是javax.servlet.http.HttpServletRequest;
Session session = null;
HttpSession httpSession = request.getSession(false);//先从request里获得本来存在的
if (httpSession != null) {
session = createSession(httpSession, request.getRemoteHost());//如果不为空,就创建一个封装了的,为空就不管它
}
return session;
}
可以看看注释,注释上都写好了,这里的意思是,首先判断封装好的Key是不是Http的key,如果是就继续,不是就抛异常.key这个是带了ServletRequest和ServletResponse的WebSessinKey,具体怎么new出来的,看DefaultWebSecurityManager的这方法代码就知道了(代码1-3),这里里,将Request和Response还有sessionId传递进去
(代码1-3)
@Override
protected SessionKey getSessionKey(SubjectContext context) {
if (WebUtils.isWeb(context)) {
Serializable sessionId = context.getSessionId();
ServletRequest request = WebUtils.getRequest(context);
ServletResponse response = WebUtils.getResponse(context);
return new WebSessionKey(sessionId, request, response);
} else {
return super.getSessionKey(context);
}
}
因为继承了 org.apache.shiro.session.mgt.DefaultSessionKey ,这个类是实现了SessionKey这个接口。
看看createSession这个方法,这个方法就是将传入的HttpSession和host传进去,封装成Shiro的HttpServletSession (代码 1-4)
(代码1-4)
protectedSession createSession(HttpSession httpSession, String host) {
return new HttpServletSession(httpSession, host);
}
关于默认的Session管理器,最后还看一下HttpServletSession这个类,就看一下的几个方法,还有些方法是没有放出来的,可以明显的看出,Shiro使用了一个叫Session的接口,但这个接口是和HttpSession的接口一模一样,就是通过HttpSession这个接口获得Session的功能(代码1-5)
(代码1-5)
public class HttpServletSession implements Session {
private HttpSession httpSession = null;
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);
}
}
……………………
……………………
………………
public Object getAttribute(Object key) throws InvalidSessionException {
try {
return httpSession.getAttribute(assertString(key));
} catch (Exception e) {
throw new InvalidSessionException(e);
}
}
public void setAttribute(Object key, Object value) throws InvalidSessionException {
try {
httpSession.setAttribute(assertString(key), value);
} catch (Exception e) {
throw new InvalidSessionException(e);
}
}
………………
………………
}
默认的模式就描述到这里
2、接下来说第二种方式,就是废弃掉tomcat自己的Session,使用企业级Session方案,这种方案可以和容器无关,但在我们项目没有使用,因为用了开源连接池druid貌似logout的时候有点不对。
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="3600000" />
<property name="sessionDAO" ref="sessionDAO" />
</bean>
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<!-- 用户授权信息Cache, 采用EhCache -->
<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="cacheManager"/>
</bean>
必须向Spring注册EnterpriseCacheSessionDAO然后将cacheManager注入进去
这种配置的创建入口在SecurityUtils.getSubject().getSession();这里看下面代码(代码2-1)
(代码2-1)
publicSession 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) {//session为空,并且能创建
……
……省略
……
SessionContext sessionContext = createSessionContext();
Session session = this.securityManager.start(sessionContext);//在这里创建Session
this.session = decorate(session);//包装Session,他自己建的自己也去包装一下
}
调用DefaultSecurityManager的父类SessionsSecurityManager的Sessionstart(SessionContext context),下面是这个方法的代码(代码2-2)
(代码2-2)
public Session start(SessionContext context) throws AuthorizationException {
return this.sessionManager.start(context);
}
然后调用sessionManager的start方法来创建Session。创建Session的入口,就在这里。看下面代码分析(代码2-3)
(代码2-3)
publicSession start(SessionContext context) {
Session session = createSession(context);//创建Session
applyGlobalSessionTimeout(session);
onStart(session, context);
notifyStart(session);
return createExposedSession(session, context);
}
创建Session这里是调用DefaultSessionManager的父类的createSession,其实父类也没有真正来创建Session。这里用到了模板方法,父类里面的doCreateSession是抽象方法,最后真正创建子类的还是交给子类去实现(代码2-4)
(代码2-4)
protectedSession createSession(SessionContext context) throwsAuthorizationException {
enableSessionValidationIfNecessary();
return doCreateSession(context);
}
protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
其他的也没多少可分析的。这里再看一下manager里面的sessionFacotry工厂的createSession方法(代码2-5)
(代码2-5)
publicSession createSession(SessionContext initData) {
if (initData != null) {
String host = initData.getHost();
if (host != null) {
return new SimpleSession(host);
}
}
return new SimpleSession();
}
这里的SimpleSession是实现了Session接口的。具体可以看看相关的类继承图
另外Session是怎么缓存进入Cache的呢?在下面的调用下面代码创建Session的过程中,以下方法会调用,而缓存就在create(s)这里面(代码2-6)
(代码2-6)
protected Session doCreateSession(SessionContext context) {
Session s = newSessionInstance(context);
if (log.isTraceEnabled()) {
log.trace("Creating session for host {}", s.getHost());
}
create(s);
return s;
}
经过一些步骤之后在CachingSessionDao里被缓存,下面是代码。可以看下面的注释(代码2-7)
(代码2-7)
protectedvoid cache(Session session, Serializable sessionId) {
if (session == null || sessionId == null) {
return;
}
Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();//获取缓存
if (cache == null) {
return;
}
cache(session, sessionId, cache);//有缓存就存起来
}
以上是Session的创建过程,获取Session就简单说吧,有些过程自己发现更有趣。这里会调用DefaultWebSessionManager的父类的getAttribute这个方法(代码2-8)
(代码2-8)
publicObject getAttribute(SessionKey sessionKey, Object attributeKey)throws InvalidSessionException {
return lookupRequiredSession(sessionKey).getAttribute(attributeKey);
}
最后会调用CachingSessionDao的这个publicSession readSession(Serializable sessionId) throwsUnknownSessionException 在这里就会从缓存里读取Session(代码2-9)
(代码2-9)
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = getCachedSession(sessionId);
if (s == null) {
s = super.readSession(sessionId);
}
return s;
}
这是getCachedSession(sessionId)的代码。看了代码想必很容易理解了吧(代码2-10)
protected Session getCachedSession(Serializable sessionId) {
Session cached = null;
if (sessionId != null) {
Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
if (cache != null) {
cached = getCachedSession(sessionId, cache);
}
}
return cached;
}
获得了Session,要获得里面的值和对象就很容易了
有问题欢迎提出来,因为是先写在编辑器上的,然后在拷贝到word上,所以代码是一致的黑色,希望能够讲究着看,写个原创文章不容易,眼睛都看肿了,所以转载的时候能带上作者,谢谢。