shiro安全控制目录
session一般是存储在单机的内存中,但是在集群环境中,session可以存储在公用的缓存服务器上。本文主要介绍通过Shiro管理session。Shiro下的session管理。并将session缓存到redis中。这样就可以在集群环境中共享session。
1. XML配置
在web.xml配置DelegatingFilterProxy拦截器代理,将拦截器交由Spring容器管理,Spring bean默认使用
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
2. shiro配置
这个就是由spring管理的拦截器。其中的会话管理器sessionManager
,由安全管理器securityManager
进行配置
/login/** = anon
/static/** = anon
/** = user
3. securityManager配置
自定义Realm的理解中Realm实现了认证和授权,而sessionManager实现了用户自定义的会话管理。一般在web中,有两种会话管理器:
(1)DefaultWebSessionManager(由shiro维护会话)
(2)ServletContainerSessionManager(由servlet维护会话)
无论采取哪种会话管理,需要注意的是subject和request获取的session是同一个session。
3. defaultWebSessionManager配置
用户自定义的sessionManager对象,用户可以自定义sessionDAO
对象,完成session对象的持久化。
需要注意的参数
- Shiro配置文件参数含义
- shiro的会话验证调度器SessionValidationScheduler详解
4. redisShiroSessionDAO配置
我们要将Redis和Session进行整合,就要实现SessionDAO接口。实现SessionDAO和AbstractSessionDAO的方法。
2. 整合Redis集群
有些小伙伴是将Redis的配置写到xml中,jedisCluster的配置方式,但是此处采取的自定义一个类来组装Redis连接参数。并且也可以看到采用的是
init-method="init"
,即在项目初始化的时候,执行该配置方法。
1. Redis集群接口
public interface IJedisCluster {
/**
* 获取redis集群对象
*
* @return
*/
JedisCluster getJedis();
int getDataTimeOut();
T get(String key, Class requiredType);
void set(String key, Object valueObj);
String get(String key);
void set(String key, String value);
void del(String key);
Set keys(String key);
/**
* 指定的 key 设置值及其过期时间。如果 key已经存在, SETEX命令将会替换旧的值
* @param key
* @param valueObj
* @param seconds
*/
void setex(String key ,Object valueObj,int seconds);
void setexString(String key ,String valueObj,int seconds);
/**
* 设置 key对应的值为 string类型的 value。 如果key已经存在,返回 0,不存在返回1
* @param key
* @param value
* @param expireTime
* @return
*/
int setnx(String key, String value, int expireTime);
}
2. 自定义Redis配置类
public class MyJedisCluster implements IJedisCluster {
private static final Logger log = LoggerFactory.getLogger(MyJedisCluster.class);
/**
* 默认超时时间
*/
public static final int DEFALT_TIMEOUT = 6000;
/**
* 默认最大重定向数
*/
public static final int DEFALT_MAX_REDIRECTIONS = 5;
/**
* 默认最大连接数
*/
public static final int DEFALT_MAX_TOTAL = 5;
/**
* 默认最大空闲数
*/
public static final int DEFALT_MAX_IDLE = 5;
/**
* 默认最小空闲数
*/
public static final int DEFALT_MIN_IDLE = 5;
/**
* 主机关键字
*/
public static final String KEY_HOST = "host";
/**
* 端口关键字
*/
public static final String KEY_PORT = "port";
/**
* 密码
*/
private String password ;
/**
* 返回值得超时时间
*/
private int soTimeout;
/**
* 是否适用密码
*/
private boolean usePwd;
/**
* Redis连接测试KEY
*/
private static final String CONN_TEST_KEY = "test:redis";
/**
* 超时时间
*/
private int timeout;
/**
* 会话超时时间
*/
private int dataTimeOut;
/**
* 最大重定向数
*/
private int maxRedirections;
/**
* 集群节点集合
*/
private List
3. 工具类
public class SerializeUtil {
public static byte[] serialize(Object value) {
if (value == null) {
throw new NullPointerException("Can't serialize null");
}
byte[] rv = null;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
try {
bos = new ByteArrayOutputStream();
os = new ObjectOutputStream(bos);
os.writeObject(value);
os.close();
bos.close();
rv = bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
System.out.println("serialize error");
}
return rv;
}
public static Object deserialize(byte[] in) {
return deserialize(in, Object.class);
}
@SuppressWarnings("unchecked")
public static T deserialize(byte[] in, Class requiredType) {
Object rv = null;
ByteArrayInputStream bis = null;
ObjectInputStream is = null;
try {
if (in != null) {
bis = new ByteArrayInputStream(in);
is = new ObjectInputStream(bis);
rv = is.readObject();
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("deserialize error");
}
return (T) rv;
}
}
JDK1.7以上,流实现的是
AutoCloseable
接口,即执行完try方法(即使执行失败)自动关闭资源。
3. JedisManager的管理
获取JedisCluster集群对象,完成增该删查等Redis操作。
public class JedisManager {
//Spring在配置文件中注入的
private MyJedisCluster myJedisCluster;
public String getValueBykey(String keyStr){
JedisCluster jedisCluster = myJedisCluster.getJedis();
return jedisCluster.get(keyStr);
}
public void deleteByKey(String keyStr){
JedisCluster jedisCluster = myJedisCluster.getJedis();
jedisCluster.del(keyStr);
}
public void setValueByKey(String keyStr, String valueStr, int expireTime) {
JedisCluster jedisCluster = myJedisCluster.getJedis();
jedisCluster.set(keyStr, valueStr);
if (expireTime > 0) {
jedisCluster.expire(keyStr, expireTime);
}
}
public MyJedisCluster getMyJedisCluster() {
return myJedisCluster;
}
public void setMyJedisCluster(MyJedisCluster myJedisCluster) {
this.myJedisCluster = myJedisCluster;
}
}
4. ShiroSessionRepository的管理
在得到的JedisManager之后,开始操作Shiro的Session处理。
1. 定义ShiroSessionRepository接口
对Session操作的接口。
public interface ShiroSessionRepository {
void saveSession(Session session);
void deleteSession(Serializable sessionId);
Session getSession(Serializable sessionId);
Collection getAllSessions();
}
2. 使用Redis实现上面的接口
public class JedisShiroSessionRepository implements ShiroSessionRepository {
private Logger logger = LoggerFactory.getLogger(JedisShiroSessionRepository.class);
private JedisManager jedisManager;
//将SessionId:Session存入Redis中
public void saveSession(Session session) {
if (session == null || session.getId() == null)
throw new NullPointerException("session is empty");
try {
byte[] key = SerializeUtil.serialize(buildRedisSessionKey(session.getId()));
byte[] value = SerializeUtil.serialize(session);
String keyStr = Base64.encode(key);
String valueStr = Base64.encode(value);
int expireTime = Constant.SESSION_VAL_TIME_SPAN;
jedisManager.setValueByKey(keyStr, valueStr, expireTime);
freshSessionId(session);
} catch (Exception e) {
e.printStackTrace();
System.out.println("save session error");
}
}
public void deleteSession(Serializable id) {
if (id == null) {
throw new NullPointerException("session id is empty");
}
try {
byte[] idByte = SerializeUtil.serialize(buildRedisSessionKey(id));
String idStr = Base64.encode(idByte);
jedisManager.deleteByKey(idStr);
} catch (Exception e) {
e.printStackTrace();
System.out.println("delete session error");
}
}
public Session getSession(Serializable id) {
if (id == null)
throw new NullPointerException("session id is empty");
Session session = null;
try {
byte[] idByte = SerializeUtil.serialize(buildRedisSessionKey(id));
String idStr = Base64.encode(idByte);
String valueStr = jedisManager.getValueBykey(idStr);
if(valueStr != null){
byte[] valueByte = Base64.decode(valueStr);
session = SerializeUtil.deserialize(valueByte, Session.class);
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("get session error");
}
return session;
}
public Collection getAllSessions() {
Set sessions = new HashSet();
//TODO,查询Redis中Constant.REDIS_SHIRO_SESSION前缀的变量。放入Set中。
return sessions;
}
//Constant.REDIS_SHIRO_SESSION前缀
private String buildRedisSessionKey(Serializable sessionId) {
return Constant.REDIS_SHIRO_SESSION + sessionId;
}
public JedisManager getJedisManager() {
return jedisManager;
}
public void setJedisManager(JedisManager jedisManager) {
this.jedisManager = jedisManager;
}
private void freshSessionId(Session session) {
//Session中获取userId
Object userId= session.getAttribute(Constant.GET_USER_ID);
if(accountObj == null) {
return;
}
String sessionId = null;
try {
sessionId= Base64.encode(SerializeUtil.serialize(buildRedisSessionKey(session.getId())));
} catch (Exception e) {
logger.error("刷新sessionId出错", e);
}
//将userId和sessionId保存到Redis中。
jedisManager.setValueByKey("IS_LOGIN" + userId, sessionId, Constant.SESSION_VAL_TIME_SPAN);
}
}
5. 实现CustomShiroSessionDAO接口
实现AbstractSessionDAO接口,将Session保存在Redis中。
public class CustomShiroSessionDAO extends AbstractSessionDAO {
private ShiroSessionRepository shiroSessionRepository;
public void update(Session session) throws UnknownSessionException {
BASE64Encoder encoder = new BASE64Encoder();
getShiroSessionRepository().saveSession(session);
}
public void delete(Session session) {
if (session == null) {
return;
}
Serializable id = session.getId();
if (id != null) {
getShiroSessionRepository().deleteSession(id);
}
//TODO if session is too large,when session destory clear shiro cache
}
public Collection getActiveSessions() {
return getShiroSessionRepository().getAllSessions();
}
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
getShiroSessionRepository().saveSession(session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
return getShiroSessionRepository().getSession(sessionId);
}
public ShiroSessionRepository getShiroSessionRepository() {
return shiroSessionRepository;
}
public void setShiroSessionRepository(
ShiroSessionRepository shiroSessionRepository) {
this.shiroSessionRepository = shiroSessionRepository;
}
}
最终实现的效果是:操作session信息最终保存到了Redis中。
推荐阅读:
https://www.sojson.com/blog/137.html
https://www.cnblogs.com/jiangkuan/p/6189136.html
jedisCluster的配置方式