今天给大家分享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("反序列化失败");
? ? ? }
? }
?
}
|