今天给大家分享session缓存配置与session持久化示例
1. shiro中的缓存
在权限验证时每次从数据库中获取登陆权限数据显然是不合适的,更合适方式是将数据缓存到内存,以提高系统性能。
1.1 引入jar包
<!-- 缓存需要的包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring-version}</version>
</dependency>
1.2 ehcache配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir"/>
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
<!--name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)-->
<!-- <cache name="stuCache" eternal="false" maxElementsInMemory="100"
overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU"/> -->
</ehcache>
1.2 配置spring-base的配置文件
<!-- shiro 缓存-->
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
<property name="shared" value="true"></property>
</bean>
<bean id="shrioEhcache" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManager" ref="cacheManagerFactory"></property>
</bean>
<!-- 将自定义的realm注入到安全管理器中 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroRealm" />
<!-- 为安全管理器配置缓存 -->
<property name="cacheManager" ref="shrioEhcache"></property>
</bean>
2.shiro中的session
2.1简单的小案例
session中保存的数据,只要session未结束,在任何地方都可以访问session值
必须要先登陆用户,使session里面有用户登陆的值,然后再访问session的值。
mapper不用编写,因为咱们目的只是测试session获取值
service层
@Service
public class SessionService implements ISession{
@Override
public void Sessiontest() {
Session session = SecurityUtils.getSubject().getSession();
System.out.println("sesion is"+session.getAttribute("user"));
}
}
首先,登陆的Controller保存session的值,然后通过编写SessionController获取session的值
@RequestMapping("user/login")
public String login(User user, Model model, HttpSession session) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),user.getPassword());
try {
subject.login(token);
session.setAttribute("user",user);
} catch (UnknownAccountException | LockedAccountException e) {
model.addAttribute("message", e.getMessage());
return "login";
} catch (AuthenticationException e) {
e.printStackTrace();
model.addAttribute("message", "密码错误");
return "login";
}
return "index";
}
编写一个简单SessionController得到session的值
@Controller
public class SessionController {
@Autowired
private ISession iSession;
@RequestMapping("Sessiontest")
public Object Sessiontest(){
iSession.Sessiontest();
return "";
}
}
效果展示
3.Session监听
效果展示
用于监听session的创建,过期等事件,如果在需要时可以再session创建时做些初始化操作,或在过期时做些清理操作。
1) 创建一个自定义监听器
@Slf4j//做为日志输出
public class SessionListener extends SessionListenerAdapter {
@Override
public void onStart(Session session) {
log.info("Shiro session onStart .... ");
}
@Override
public void onStop(Session session) {
log.info("Shiro session onStop .... ");
}
@Override
public void onExpiration(Session session) {
log.info("Shiro session onExpiration ....");
}
}
2)配置文件,在spring配置文件中做如下配置
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="shiroRealm" />
<!-- 注入缓存管理器 -->
<property name="cacheManager" ref="shrioEhcache"/>
<!-- session管理器 -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- session管理器 ,配置自定义监听器,同时需要将该sessionManager配置到securityManager中-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionListeners">
<list>
<bean class="com.zking.shirodemo.listener.SessionListener"/>
</list>
</property>
</bean>
4.session持久化
登陆后
由此可见,我们日志只打印了一个sql语句,说明只在数据库执行一次
1)session持久化在applicationContext-base.xml配置文件
<!-- session管理器 ,配置自定义监听器,同时需要将该sessionManager配置到securityManager中-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionListeners">
<list>
<bean class="com.zking.spring.listener.SessionListener"/>
</list>
</property>
<!-- 配置管理session的 dao -->
<property name="sessionDAO" ref="sessionDao"/>
</bean>
<!-- 自定义SessionDao,将session持久化到数据库, 需要将该Bean注入到sessionManager -->
<bean id="sessionDao" class="com.zking.spring.listener.DbSessionDao">
</bean>
2)将session持久化加入安全管理器
<!--注册安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--自定义Reaml(登陆认证,登陆授权)-->
<property name="realm" ref="shiroRealm" />
<!--shiro 缓存-->
<property name="cacheManager" ref="shrioEhcache"></property>
<!--session持久监听器-->
<property name="sessionManager" ref="sessionManager"/>
</bean>
3)实现序列化接口,文章最后会附上序列化类
4)因为session要写CRUD操作,mapper层service层就不显示了
5)编写自定义的session,实现持久化必须要实现EnterpriseCacheSessionDAO接口,重写父类CRUD方法。
/**
* 自定义Session持久化,将Shiro的Session数据保存到数据库中。
* 完成该类的编写后,需要在spring配置文件中进行配置
*/
@Slf4j
public class DbSessionDao extends EnterpriseCacheSessionDAO {
@Autowired
private ISessionModel sessionService;
@Override
protected Serializable doCreate(Session session) {
Serializable sid = super.doCreate(session);
SessionModel model = new SessionModel();
model.setSessionId(sid.toString());
model.setSession(SerializableUtil.serialize(session));
log.debug("将session保存到数据库, sessionId = {}", sid);
sessionService.addSession(model);
return sid;
}
@Override
protected Session doReadSession(Serializable sessionId) {
Session session = super.doReadSession(sessionId);
//如果从内存中获取了session,则直接返回
if (!Objects.isNull(session)) {
log.debug("从内存中获取session,sessionId = " + sessionId + ", 直接返回");
return session;
}
log.debug("从内存中没有获取到session,id={}, 将从数据库获取session", sessionId);
SessionModel model = new SessionModel();
model.setSessionId(sessionId.toString());
session = (Session) sessionService.getSession(model);
if(Objects.isNull(session)) {
log.debug("数据库中也没有找到id={}的session,将返回null");
}
return session;
}
//删除session时,需要将数据表中的记录一并删除
@Override
protected void doDelete(Session session) {
SessionModel model = new SessionModel();
model.setSessionId(session.getId().toString());
log.debug("删除session,sessionId: " + session.getId().toString());
sessionService.delSession(model);
super.doDelete(session);
}
//更新session
@Override
protected void doUpdate(Session session) {
String sessionId = session.getId().toString();
SessionModel tmpModel = new SessionModel();
tmpModel.setSessionId(sessionId);
SessionModel model = sessionService.getSession(tmpModel);
if(Objects.isNull(model)) {
Object obj = session.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
//数据库中是否有session,如果没有检查session无效,则直接返回,否则保存到数据库中
if(Objects.isNull(obj) || !Boolean.parseBoolean(obj.toString())) {
return ;
}
SessionModel saveModel = new SessionModel();
saveModel.setSessionId(session.getId().toString());
saveModel.setSession(SerializableUtil.serialize(session));
log.debug("session已经过验证,且在数据库中不存在,将session保存到数据库 ..... ");
sessionService.addSession(saveModel);
} else {
//如果session在数据库中已存在,则更新session
model.setSession(SerializableUtil.serialize(session));
log.debug("session在数据库中已存在,将session更新到数据库 ..... ");
sessionService.updateSession(model);
}
//调用父类方法,更新session
super.doUpdate(session);
}
}
附1:序列化实现类
public final class SerializableUtil {
private SerializableUtil() {}
/**
* Session序列化
* @param session 待序列化的session
* @return String
*/
public static String serialize(Session session) {
try {
//ByteArrayOutputStream 用于存储序列化的Session对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//将Object对象输出成byte数据
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(session);
//将字节码,编码成String类型数据
return Base64.getEncoder().encodeToString(bos.toByteArray());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("序列化失败");
}
}
/**
* session的反向序列化
* @param sessionString 需要被反向序列化的对象
* @return
*/
public static Session deserialize(String sessionString) {
try {
//读取字节码表
ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(sessionString));
//将字节码反序列化成 对象
ObjectInputStream in = new ObjectInputStream(bis);
Session session = (Session) in.readObject();
return session;
} catch (Exception e) {
throw new RuntimeException("反序列化失败");
}
}
}