原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/80418112 ©王赛超
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。即直接使用 Shiro 的会话管理可以直接替换如 Web 容器的会话管理。
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
与web中的 HttpServletRequest.getSession(boolean create) 类似!
Subject.getSession(true)。即如果当前没有创建session对象会创建一个;
Subject.getSession(false),如果当前没有创建session对象则返回null。
返回值 | 方法名 | 描述 |
---|---|---|
Object | getAttribute(Object key) | 根据key标识返回绑定到session的对象 |
Collection | getAttributeKeys() | 获取在session中存储的所有的key |
String | getHost() | 获取当前主机ip地址,如果未知,返回null |
Serializable | getId() | 获取session的唯一id |
Date | getLastAccessTime() | 获取最后的访问时间 |
Date | getStartTimestamp() | 获取session的启动时间 |
long | getTimeout() | 获取session失效时间,单位毫秒 |
void | setTimeout(long maxIdleTimeInMillis) | 设置session的失效时间 |
Object | removeAttribute(Object key) | 通过key移除session中绑定的对象 |
void | setAttribute(Object key, Object value) | 设置session会话属性 |
void | stop() | 销毁会话 |
void | touch() | 更新会话最后访问时间 |
会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionsSecurityManager。
SecurityManager提供了如下接口:
Session start(SessionContext context);
//启动会话
Session getSession(SessionKey key) throws SessionException;
//根据会话Key获取会话
另外用于Web环境的WebSessionManager又提供了如下接口:
boolean isServletContainerSessions();
//是否使用Servlet容器的会话
Shiro还提供了ValidatingSessionManager用于验资并过期会话:
void validateSessions();
//验证所有会话是否过期
Shiro提供了三个默认实现:
DefaultSessionManager:
DefaultSecurityManager使用的默认实现,用于JavaSE环境;
ServletContainerSessionManager:
DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;
DefaultWebSessionManager:
用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。
package com.springboot.test.shiro.config.shiro;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: wangsaichao
* @date: 2018/5/15
* @description: 配置session监听器
*/
public class ShiroSessionListener implements SessionListener{
/**
* 统计在线人数
* juc包下线程安全自增
*/
private final AtomicInteger sessionCount = new AtomicInteger(0);
/**
* 会话创建时触发
* @param session
*/
@Override
public void onStart(Session session) {
//会话创建,在线人数加一
sessionCount.incrementAndGet();
}
/**
* 退出会话时触发
* @param session
*/
@Override
public void onStop(Session session) {
//会话退出,在线人数减一
sessionCount.decrementAndGet();
}
/**
* 会话过期时触发
* @param session
*/
@Override
public void onExpiration(Session session) {
//会话过期,在线人数减一
sessionCount.decrementAndGet();
}
/**
* 获取在线人数使用
* @return
*/
public AtomicInteger getSessionCount() {
return sessionCount;
}
}
/**
* 配置session监听
* @return
*/
@Bean("sessionListener")
public ShiroSessionListener sessionListener(){
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}
/**
* 配置会话ID生成器
* @return
*/
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
/**
* SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件
* MemorySessionDAO 直接在内存中进行会话维护
* EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
* @return
*/
@Bean
public SessionDAO sessionDAO() {
EnterpriseCacheSessionDAO enterpriseCacheSessionDAO = new EnterpriseCacheSessionDAO();
//使用ehCacheManager
enterpriseCacheSessionDAO.setCacheManager(ehCacheManager());
//设置session缓存的名字 默认为 shiro-activeSessionCache
enterpriseCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
//sessionId生成器
enterpriseCacheSessionDAO.setSessionIdGenerator(sessionIdGenerator());
return enterpriseCacheSessionDAO;
}
/**
* 配置保存sessionId的cookie
* 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
* @return
*/
@Bean("sessionIdCookie")
public SimpleCookie sessionIdCookie(){
//这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("sid");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
/**
* 配置会话管理器,设定会话超时及保存
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection listeners = new ArrayList();
//配置监听
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setCacheManager(ehCacheManager());
return sessionManager;
}
注意:
这里的SessionIdCookie 是新建的一个SimpleCookie对象,不是之前整合记住我的那个rememberMeCookie 如果配错了,就会出现session经典问题:每次请求都是一个新的session 并且后台报以下异常,解析的时候报错.因为记住我cookie是加密的用户信息,所以报解密错误
org.apache.shiro.crypto.CryptoException: Unable to execute 'doFinal' with cipher instance [javax.crypto.Cipher@461df537].
/**
* 配置核心安全事务管理器
* @return
*/
@Bean(name="securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义realm.
securityManager.setRealm(shiroRealm());
//配置记住我 参考博客:
securityManager.setRememberMeManager(rememberMeManager());
//配置 ehcache缓存管理器 参考博客:
securityManager.setCacheManager(ehCacheManager());
//配置自定义session管理,使用ehcache 或redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
配置完成之后启动测试,登陆的时候点击 rememberMe 查看cookie 可以看到一个sessionId 和 一个记住我cookie
以上整合会话管理,还有一个问题: 如果用户如果不点注销,直接关闭浏览器,不能够进行session的清空处理,所以为了防止这样的问题,还需要增加有一个会话的验证调度。
修改sessionManager如下:
/**
* 配置会话管理器,设定会话超时及保存
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection listeners = new ArrayList();
//配置监听
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setCacheManager(ehCacheManager());
//全局会话超时时间(单位毫秒),默认30分钟 暂时设置为10秒钟 用来测试
sessionManager.setGlobalSessionTimeout(1800000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
sessionManager.setSessionValidationInterval(3600000);
return sessionManager;
}
测试方法:
设置 全局session超时时间setGlobalSessionTimeout为 10000 ,设置session的失效扫描时间setSessionValidationInterval为5000,然后测试
结果如下:
后台每5秒会打印日志 显示 如下:
2018/05/23 11:20:11.516 o.a.s.s.m.ExecutorServiceSessionValidationScheduler [] DEBUG Executing session validation...
2018/05/23 11:20:11.516 o.a.s.s.m.AbstractValidatingSessionManager [] INFO Validating all active sessions...
2018/05/23 11:20:11.516 o.a.s.w.s.m.DefaultWebSessionManager [] DEBUG SessionKey argument is not HTTP compatible or does not have an HTTP request/response pair. Session ID cookie will not be removed due to invalidated session.
2018/05/23 11:20:11.516 o.a.s.s.m.AbstractValidatingSessionManager [] DEBUG Invalidated session with id [2e9e317f-7575-4bb0-98c4-3e6e5d2578f5] (expired)
2018/05/23 11:20:11.516 o.a.s.s.m.AbstractValidatingSessionManager [] INFO Finished session validation. [1] sessions were stopped.
2018/05/23 11:20:11.516 o.a.s.s.m.ExecutorServiceSessionValidationScheduler [] DEBUG Session validation completed successfully in 0 milliseconds.
session过期之后 再点击连接跳转到首页,并且后台报错 提示 找不到session
org.apache.shiro.session.UnknownSessionException: There is no session with id [2e9e317f-7575-4bb0-98c4-3e6e5d2578f5]
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
cache>
注意:
这里一定要注意缓存的设置过期时间,还有setGlobalSessionTimeout
的值,任一个时间设置的比较短,session就会从ehcache中清除,到时候就会报
There is no session with id [2e9e317f-7575-4bb0-98c4-3e6e5d2578f5]
//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
/**
* 配置会话管理器,设定会话超时及保存
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection listeners = new ArrayList();
//配置监听
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setCacheManager(ehCacheManager());
//全局会话超时时间(单位毫秒),默认30分钟 暂时设置为10秒钟 用来测试
sessionManager.setGlobalSessionTimeout(10000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
sessionManager.setSessionValidationInterval(5000);
//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;
}