如果是单机应用,session共享意义不大。使用默认的session缓存,还是存放到第三方应用缓存中,都可以。当然极端情况下,如果服务器内存非常小等极特殊情况下可能需要第三方缓存的。
session共享是针对集群(或分布式、或分布式集群)的情况下采用;如果不做session共享,仍然采用默认的方式(session存放到默认的servlet容器),当我们的应用是以集群的方式发布的时候,同个用户的请求会被分发到不同的集群节点(分发依赖具体的负载均衡规则),那么每个处理同个用户请求的节点都会重新生成该用户的session,这些session之间是毫无关联的。那么同个用户的请求会被当成多个不同用户的请求,这肯定是不行的。
实现共享session是比较简单的,换一种说明你就能明白。大家都会增、删、改、查,session的操作就是增删改查的过程,只不过默认是缓存到servlet容器中,咱们要将数据转移到redis,来实现它的增删改查。这样在集群环境中,大家都访问这个redis,也就实现了共享session.
下面首先要了解下 shiro创建和缓存session的过程。
protected abstract Serializable doCreate(Session session);
public void delete(Session session) {
uncache(session);
doDelete(session);
}
public void update(Session session) throws UnknownSessionException {
doUpdate(session);
if (session instanceof ValidatingSession) {
if (((ValidatingSession) session).isValid()) {
cache(session, session.getId());
} else {
uncache(session);
}
} else {
cache(session, session.getId());
}
}
protected abstract Session doReadSession(Serializable sessionId);
从源码中可以看出,共享sessionId说白了就是改变增删改查保存的位置。
默认session是保存的servlet缓存中,进行增删改查,现在咱们覆写方法,把增删改查的数据源改为redis。
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
/**
* @Author wangy
* @create 2022/3/19 13:47
* @Description 实现sessionDao,从而将session信息保存到redis中,达到集群环境共享session目的
*/
@Component
public class RedisSessionDao extends AbstractSessionDAO {
/**
* Session超时时间,单位为毫秒 当前设置半个小时
*/
private long expireTime = 1800000;
/**
* 注入Redis操作类
*/
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合
* @return
*/
@Override
public Collection<Session> getActiveSessions() {
return redisTemplate.keys("*");
}
/**
* 新增和保存session到redis中
* @param session
* @return
*/
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
return sessionId;
}
/**
* 读取redis中的sessioin
* @param sessionId
* @return
*/
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
return null;
}
Session session = (Session) redisTemplate.opsForValue().get(sessionId);
return session;
}
/**
* 用户请求接口,然后修改session的有效期
* @param session
*/
@Override
public void update(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
return;
}
//设置超时时间,这个是毫秒
session.setTimeout(expireTime);
redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS);
}
/**
* session到期后删除session,比如说退出登录 logout
* @param session
*/
@Override
public void delete(Session session) {
if (null == session) {
return;
}
redisTemplate.opsForValue().getOperations().delete(session.getId());
}
}
这个类的代码中实现了对session的增删改查操作,大家应该到这比较容易理解了。
@Bean
public DefaultWebSecurityManager securityManager(RedisCachingSessionDao redisCachingSessionDao) {
//新建security并设置realm、SessionManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(new AdminAuthorizingRealm());
//新建SessionManager并设置SessionDao(session的获取途径)
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisCachingSessionDao);
//应用sessionManager
securityManager.setSessionManager(sessionManager);
return securityManager;
}
将自己的实现类redisCachingSessionDao,通过代码set到sessionManager中。
这样自己创建session后,可以从redis中查到自己的session数据了,共享session完成了,简单吧!
共享session已经实现了,集群环境中能够访问相同的session数据源,但是shiro仍会有一些数据会缓存在servlet容器中,这样集群环境会出现一些其他的shiro配置数据各自用的还是各自的,出现各种各样问题。所以后面还需要解决 共享缓存的问题。
用到下面一些类:
package org.apache.shiro.cache;
public interface CacheManager {
<K, V> Cache<K, V> getCache(String var1) throws CacheException;
}
public interface Cache<K, V> {
V get(K var1) throws CacheException;
V put(K var1, V var2) throws CacheException;
V remove(K var1) throws CacheException;
void clear() throws CacheException;
int size();
Set<K> keys();
Collection<V> values();
}
同样道理,咱们继承这个类,并覆写这个类的增删改查,从缓存到servlet,转移到redis中。
下面咱们定义自己的实现类
3. ShiroRedisCache
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @Author wangy
* @create 2022/3/19 11:55
* @Description 缓存的实现类 shiro所有的缓存数据,都会存到redis中
*/
@Component
public class ShiroRedisCache<K,V> implements Cache<K,V>{
/**
* redis操作类
*/
@Autowired
private RedisTemplate<K,V> redisTemplate;
/**
* 定义缓存生效时间 为半个小时
*/
private long expireTime = 1800;
/**
* 查询 操作
*/
@Override
public V get(K k) throws CacheException {
return redisTemplate.opsForValue().get(k);
}
/**
* 新增 操作
*/
@Override
public V put(K k, V v) throws CacheException {
redisTemplate.opsForValue().set(k,v,expireTime, TimeUnit.SECONDS);
return null;
}
/**
* 删除 操作
*/
@Override
public V remove(K k) throws CacheException {
V v = redisTemplate.opsForValue().get(k);
redisTemplate.opsForValue().getOperations().delete(k);
return v;
}
@Override
public void clear() throws CacheException {
}
@Override
public int size() {
return 0;
}
@Override
public Set<K> keys() {
return null;
}
@Override
public Collection<V> values() {
return null;
}
}
然后再次继承CacheManger,实现getCache方法
4. ShiroRedisCacheManager
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
1. @Author wangy
2. @create 2022/3/19 11:52
3. @Description 实现缓存管理manager,所有缓存从redis中取数据
*/
@Component
public class ShiroRedisCacheManager implements CacheManager {
/**
* 注入自己的redisCache
*/
@Resource
private Cache shiroRedisCache;
/**
* 覆写方法
*/
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
return shiroRedisCache;
}
}
最后将两个类set到Shrio配置中。
4. set到shiro配置类中
@Bean
public DefaultWebSecurityManager securityManager(RedisCachingSessionDao redisCahingSessionDao, ShiroRedisCacheManager shiroRedisCacheManager) {
//新建security并设置realm、CacheManager、SessionManager
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(new AdminAuthorizingRealm());
//如果使用redis共享session,这个必须设置,因为集群之中 session要共享,同样一些缓存的数据也要共享,比如shiro缓存的数据
securityManager.setCacheManager(shiroRedisCacheManager);
//新建SessionManager并设置SessionDao(session的获取途径)
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisCahingSessionDao);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
上面代码通过securityManager设置cacheManager属性来使用redis缓存方式。