日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

redis插件连接集群 shiro_Shiro经过Redis管理会话实现集群(转载)

發布時間:2024/9/18 数据库 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redis插件连接集群 shiro_Shiro经过Redis管理会话实现集群(转载) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

寫在前面

1.在上一篇帖子?Shiro一些補充?中提到過Shiro可以使用Shiro自己的Session或者自定義的Session來代替HttpSession

2.Redis/Jedis參考我寫的?http://sgq0085.iteye.com/category/317384 一系列內容

一. SessionDao

配置在sessionManager中,可選項,如果不修改默認使用MemorySessionDAO,即在本機內存中操作。

如果想通過Redis管理Session,從這里入手。只需要實現類似DAO接口的CRUD即可。

經過1:最開始通過繼承AbstractSessionDAO實現,發現doReadSession方法調用過于頻繁,所以改為通過集成CachingSessionDAO來實現。

注意,本地緩存通過EhCache實現,失效時間一定要遠小于Redis失效時間,這樣本地失效后,會訪問Redis讀取,并重新設置Redis上會話數據的過期時間。

因為Jedis API KEY和Value相同,同為String或同為byte[]為了方便擴展下面的方法

package com.gqshao.authentication.utils;

import com.google.common.collect.Lists;

import org.apache.commons.lang3.SerializationUtils;

import org.apache.shiro.codec.Base64;

import org.apache.shiro.session.Session;

import java.io.Serializable;

import java.util.Collection;

import java.util.List;

public class SerializeUtils extends SerializationUtils {

public static String serializeToString(Serializable obj) {

try {

byte[] value = serialize(obj);

return Base64.encodeToString(value);

} catch (Exception e) {

throw new RuntimeException("serialize session error", e);

}

}

public static Session deserializeFromString(String base64) {

try {

byte[] objectData = Base64.decode(base64);

return deserialize(objectData);

} catch (Exception e) {

throw new RuntimeException("deserialize session error", e);

}

}

public static Collection deserializeFromStringController(Collection base64s) {

try {

List list = Lists.newLinkedList();

for (String base64 : base64s) {

byte[] objectData = Base64.decode(base64);

T t = deserialize(objectData);

list.add(t);

}

return list;

} catch (Exception e) {

throw new RuntimeException("deserialize session error", e);

}

}

}

我的Dao實現,ShiroSession是我自己實現的,原因在后面說明,默認使用的是SimpleSession

package com.gqshao.authentication.dao;

import com.gqshao.authentication.session.ShiroSession;

import com.gqshao.authentication.utils.SerializeUtils;

import com.gqshao.redis.component.JedisUtils;

import org.apache.commons.lang3.StringUtils;

import org.apache.shiro.cache.Cache;

import org.apache.shiro.session.Session;

import org.apache.shiro.session.UnknownSessionException;

import org.apache.shiro.session.mgt.ValidatingSession;

import org.apache.shiro.session.mgt.eis.CachingSessionDAO;

import org.apache.shiro.subject.support.DefaultSubjectContext;

import org.apache.shiro.util.CollectionUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.Transaction;

import java.io.Serializable;

import java.util.Collection;

import java.util.List;

import java.util.Set;

/**

* 針對自定義的ShiroSession的Redis CRUD操作,通過isChanged標識符,確定是否需要調用Update方法

* 通過配置securityManager在屬性cacheManager查找從緩存中查找Session是否存在,如果找不到才調用下面方法

* Shiro內部相應的組件(DefaultSecurityManager)會自動檢測相應的對象(如Realm)是否實現了CacheManagerAware并自動注入相應的CacheManager。

*/

public class CachingShiroSessionDao extends CachingSessionDAO {

private static final Logger logger = LoggerFactory.getLogger(CachingShiroSessionDao.class);

// 保存到Redis中key的前綴 prefix+sessionId

private String prefix = "";

// 設置會話的過期時間

private int seconds = 0;

@Autowired

private JedisUtils jedisUtils;

/**

* 重寫CachingSessionDAO中readSession方法,如果Session中沒有登陸信息就調用doReadSession方法從Redis中重讀

*/

@Override

public Session readSession(Serializable sessionId) throws UnknownSessionException {

Session session = getCachedSession(sessionId);

if (session == null

|| session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {

session = this.doReadSession(sessionId);

if (session == null) {

throw new UnknownSessionException("There is no session with id [" + sessionId + "]");

} else {

// 緩存

cache(session, session.getId());

}

}

return session;

}

/**

* 根據會話ID獲取會話

*

* @param sessionId 會話ID

* @return ShiroSession

*/

@Override

protected Session doReadSession(Serializable sessionId) {

Session session = null;

Jedis jedis = null;

try {

jedis = jedisUtils.getResource();

String key = prefix + sessionId;

String value = jedis.get(key);

if (StringUtils.isNotBlank(value)) {

session = SerializeUtils.deserializeFromString(value);

logger.info("sessionId {} ttl {}: ", sessionId, jedis.ttl(key));

// 重置Redis中緩存過期時間

jedis.expire(key, seconds);

logger.info("sessionId {} name {} 被讀取", sessionId, session.getClass().getName());

}

} catch (Exception e) {

logger.warn("讀取Session失敗", e);

} finally {

jedisUtils.returnResource(jedis);

}

return session;

}

public Session doReadSessionWithoutExpire(Serializable sessionId) {

Session session = null;

Jedis jedis = null;

try {

jedis = jedisUtils.getResource();

String key = prefix + sessionId;

String value = jedis.get(key);

if (StringUtils.isNotBlank(value)) {

session = SerializeUtils.deserializeFromString(value);

}

} catch (Exception e) {

logger.warn("讀取Session失敗", e);

} finally {

jedisUtils.returnResource(jedis);

}

return session;

}

/**

* 如DefaultSessionManager在創建完session后會調用該方法;

* 如保存到關系數據庫/文件系統/NoSQL數據庫;即可以實現會話的持久化;

* 返回會話ID;主要此處返回的ID.equals(session.getId());

*/

@Override

protected Serializable doCreate(Session session) {

// 創建一個Id并設置給Session

Serializable sessionId = this.generateSessionId(session);

assignSessionId(session, sessionId);

Jedis jedis = null;

try {

jedis = jedisUtils.getResource();

// session由Redis緩存失效決定,這里只是簡單標識

session.setTimeout(seconds);

jedis.setex(prefix + sessionId, seconds, SerializeUtils.serializeToString((ShiroSession) session));

logger.info("sessionId {} name {} 被創建", sessionId, session.getClass().getName());

} catch (Exception e) {

logger.warn("創建Session失敗", e);

} finally {

jedisUtils.returnResource(jedis);

}

return sessionId;

}

/**

* 更新會話;如更新會話最后訪問時間/停止會話/設置超時時間/設置移除屬性等會調用

*/

@Override

protected void doUpdate(Session session) {

//如果會話過期/停止 沒必要再更新了

try {

if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {

return;

}

} catch (Exception e) {

logger.error("ValidatingSession error");

}

Jedis jedis = null;

try {

if (session instanceof ShiroSession) {

// 如果沒有主要字段(除lastAccessTime以外其他字段)發生改變

ShiroSession ss = (ShiroSession) session;

if (!ss.isChanged()) {

return;

}

Transaction tx = null;

try {

jedis = jedisUtils.getResource();

// 開啟事務

tx = jedis.multi();

ss.setChanged(false);

tx.setex(prefix + session.getId(), seconds, SerializeUtils.serializeToString(ss));

logger.info("sessionId {} name {} 被更新", session.getId(), session.getClass().getName());

// 執行事務

tx.exec();

} catch (Exception e) {

if (tx != null) {

// 取消執行事務

tx.discard();

}

throw e;

}

} else if (session instanceof Serializable) {

jedis = jedisUtils.getResource();

jedis.setex(prefix + session.getId(), seconds, SerializeUtils.serializeToString((Serializable) session));

logger.info("sessionId {} name {} 作為非ShiroSession對象被更新, ", session.getId(), session.getClass().getName());

} else {

logger.warn("sessionId {} name {} 不能被序列化 更新失敗", session.getId(), session.getClass().getName());

}

} catch (Exception e) {

logger.warn("更新Session失敗", e);

} finally {

jedisUtils.returnResource(jedis);

}

}

/**

* 刪除會話;當會話過期/會話停止(如用戶退出時)會調用

*/

@Override

protected void doDelete(Session session) {

Jedis jedis = null;

try {

jedis = jedisUtils.getResource();

jedis.del(prefix + session.getId());

logger.debug("Session {} 被刪除", session.getId());

} catch (Exception e) {

logger.warn("修改Session失敗", e);

} finally {

jedisUtils.returnResource(jedis);

}

}

/**

* 刪除cache中緩存的Session

*/

public void uncache(Serializable sessionId) {

Session session = this.readSession(sessionId);

super.uncache(session);

logger.info("取消session {} 的緩存", sessionId);

}

/**

* 獲取當前所有活躍用戶,如果用戶量多此方法影響性能

*/

@Override

public Collection getActiveSessions() {

Jedis jedis = null;

try {

jedis = jedisUtils.getResource();

Set keys = jedis.keys(prefix + "*");

if (CollectionUtils.isEmpty(keys)) {

return null;

}

List valueList = jedis.mget(keys.toArray(new String[0]));

return SerializeUtils.deserializeFromStringController(valueList);

} catch (Exception e) {

logger.warn("統計Session信息失敗", e);

} finally {

jedisUtils.returnResource(jedis);

}

return null;

}

public void setPrefix(String prefix) {

this.prefix = prefix;

}

public void setSeconds(int seconds) {

this.seconds = seconds;

}

}

二.Session和SessionFactory

步驟2:經過上面的開發已經可以使用的,但發現每次訪問都會多次調用SessionDAO的doUpdate方法,來更新Redis上數據,過來發現更新的字段只有LastAccessTime(最后一次訪問時間),由于會話失效是由Redis數據過期實現的,這個字段意義不大,為了減少對Redis的訪問,降低網絡壓力,實現自己的Session,在SimpleSession上套一層,增加一個標識位,如果Session除lastAccessTime意外其它字段修改,就標識一下,只有標識為修改的才可以通過doUpdate訪問Redis,否則直接返回。這也是上面SessionDao中doUpdate中邏輯判斷的意義

package com.gqshao.authentication.session;

import org.apache.shiro.session.mgt.SimpleSession;

import java.io.Serializable;

import java.util.Date;

import java.util.Map;

/**

* 由于SimpleSession lastAccessTime更改后也會調用SessionDao update方法,

* 增加標識位,如果只是更新lastAccessTime SessionDao update方法直接返回

*/

public class ShiroSession extends SimpleSession implements Serializable {

// 除lastAccessTime以外其他字段發生改變時為true

private boolean isChanged;

public ShiroSession() {

super();

this.setChanged(true);

}

public ShiroSession(String host) {

super(host);

this.setChanged(true);

}

@Override

public void setId(Serializable id) {

super.setId(id);

this.setChanged(true);

}

@Override

public void setStopTimestamp(Date stopTimestamp) {

super.setStopTimestamp(stopTimestamp);

this.setChanged(true);

}

@Override

public void setExpired(boolean expired) {

super.setExpired(expired);

this.setChanged(true);

}

@Override

public void setTimeout(long timeout) {

super.setTimeout(timeout);

this.setChanged(true);

}

@Override

public void setHost(String host) {

super.setHost(host);

this.setChanged(true);

}

@Override

public void setAttributes(Map attributes) {

super.setAttributes(attributes);

this.setChanged(true);

}

@Override

public void setAttribute(Object key, Object value) {

super.setAttribute(key, value);

this.setChanged(true);

}

@Override

public Object removeAttribute(Object key) {

this.setChanged(true);

return super.removeAttribute(key);

}

/**

* 停止

*/

@Override

public void stop() {

super.stop();

this.setChanged(true);

}

/**

* 設置過期

*/

@Override

protected void expire() {

this.stop();

this.setExpired(true);

}

public boolean isChanged() {

return isChanged;

}

public void setChanged(boolean isChanged) {

this.isChanged = isChanged;

}

@Override

public boolean equals(Object obj) {

return super.equals(obj);

}

@Override

protected boolean onEquals(SimpleSession ss) {

return super.onEquals(ss);

}

@Override

public int hashCode() {

return super.hashCode();

}

@Override

public String toString() {

return super.toString();

}

}

package com.gqshao.authentication.session;

import org.apache.shiro.session.Session;

import org.apache.shiro.session.mgt.SessionContext;

import org.apache.shiro.session.mgt.SessionFactory;

public class ShiroSessionFactory implements SessionFactory {

@Override

public Session createSession(SessionContext initData) {

ShiroSession session = new ShiroSession();

return session;

}

}

三.SessionListener

步驟3:發現用戶推出后,Session沒有從Redis中銷毀,雖然當前重新new了一個,但會對統計帶來干擾,通過SessionListener解決這個問題

package com.gqshao.authentication.listener;

import com.gqshao.authentication.dao.CachingShiroSessionDao;

import org.apache.shiro.session.Session;

import org.apache.shiro.session.SessionListener;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

public class ShiroSessionListener implements SessionListener {

private static final Logger logger = LoggerFactory.getLogger(ShiroSessionListener.class);

@Autowired

private CachingShiroSessionDao sessionDao;

@Override

public void onStart(Session session) {

// 會話創建時觸發

logger.info("ShiroSessionListener session {} 被創建", session.getId());

}

@Override

public void onStop(Session session) {

sessionDao.delete(session);

// 會話被停止時觸發

logger.info("ShiroSessionListener session {} 被銷毀", session.getId());

}

@Override

public void onExpiration(Session session) {

sessionDao.delete(session);

//會話過期時觸發

logger.info("ShiroSessionListener session {} 過期", session.getId());

}

}

四.將賬號信息放到Session中

修改realm中AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)方法,在返回AuthenticationInfo之前添加下面的代碼,把用戶信息放到Session中

// 把賬號信息放到Session中,并更新緩存,用于會話管理

Subject subject = SecurityUtils.getSubject();

Serializable sessionId = subject.getSession().getId();

ShiroSession session = (ShiroSession) sessionDao.doReadSessionWithoutExpire(sessionId);

session.setAttribute("userId", su.getId());

session.setAttribute("loginName", su.getLoginName());

sessionDao.update(session);

五.?配置文件

xmlns:util="http://www.springframework.org/schema/util"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd

http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">

Shiro安全配置

/login = authc

/logout = logout

/static/** = anon

/** = user

depends-on="lifecycleBeanPostProcessor">

maxElementsInMemory="10000"

eternal="false"

timeToLiveSeconds="60"

overflowToDisk="false"

diskPersistent="false"

diskExpiryThreadIntervalSeconds="10"

/>

六.測試會話管理

package com.gqshao.authentication.controller;

import com.gqshao.authentication.dao.CachingShiroSessionDao;

import org.apache.shiro.session.Session;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import java.io.Serializable;

import java.util.Collection;

@Controller

@RequestMapping("/session")

public class SessionController {

@Autowired

private CachingShiroSessionDao sessionDao;

@RequestMapping("/active")

@ResponseBody

public Collection getActiveSessions() {

return sessionDao.getActiveSessions();

}

@RequestMapping("/read")

@ResponseBody

public Session readSession(Serializable sessionId) {

return sessionDao.doReadSessionWithoutExpire(sessionId);

}

}

七.集群情況下的改造

1.問題上面啟用了Redis中央緩存、EhCache本地JVM緩存,AuthorizingRealm的doGetAuthenticationInfo登陸認證方法返回的AuthenticationInfo,默認情況下會被保存到Session的Attribute下面兩個字段中

org.apache.shiro.subject.support.DefaultSubjectContext.PRINCIPALS_SESSION_KEY 保存 principal

org.apache.shiro.subject.support.DefaultSubjectContext.AUTHENTICATED_SESSION_KEY 保存 boolean是否登陸

然后在每次請求過程中,在ShiroFilter中組裝Subject時,讀取Session中這兩個字段

現在的問題是Session被緩存到本地JVM堆中,也就是說服務器A登陸,無法修改服務器B的EhCache中Session屬性,導致服務器B沒有登陸。

處理方法有很多思路,比如重寫CachingSessionDAO,readSession如果沒有這兩個屬性就不緩存(沒登陸就不緩存),或者cache的session沒有這兩個屬性就調用自己實現的doReadSession方法從Redis中重讀一下。

/**

* 重寫CachingSessionDAO中readSession方法,如果Session中沒有登陸信息就調用doReadSession方法從Redis中重讀

*/

@Override

public Session readSession(Serializable sessionId) throws UnknownSessionException {

Session session = getCachedSession(sessionId);

if (session == null

|| session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {

session = this.doReadSession(sessionId);

if (session == null) {

throw new UnknownSessionException("There is no session with id [" + sessionId + "]");

} else {

// 緩存

cache(session, session.getId());

}

}

return session;

}

2.如果需要保持各個服務器Session是完全同步的,可以通過Redis消息訂閱/發布功能,再調用SessionDao中實現了刪除Session本地緩存的方法

總結

以上是生活随笔為你收集整理的redis插件连接集群 shiro_Shiro经过Redis管理会话实现集群(转载)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 日韩少妇内射免费播放18禁裸乳 | 黄色a级网站 | 欧美日韩色片 | 日韩免费精品 | 奇米精品一区二区三区在线观看 | 日韩精品色哟哟 | 亚洲AV无码成人精品区明星换面 | 中文字幕不卡在线播放 | 99日韩精品 | 蜜桃视频在线观看www | 日本精品一区二区三区四区的功能 | 日韩欧美高清在线观看 | 婷婷一区二区三区 | 亚洲精品国产av | 99re免费视频精品全部 | 欧美日韩一级在线观看 | 久久全国免费视频 | 丁香激情网 | 痴女扩张宫交脱垂重口小说 | 亚洲va久久久噜噜噜无码久久 | 夜夜爽妓女8888视频免费观看 | 国产av无码国产av毛片 | 开心激情站| 揄拍成人国产精品视频 | 性高潮久久久久久久久久 | 欧美日韩一区二区电影 | 天天搞夜夜爽 | 日韩亚洲欧美一区 | 黄色大片网站在线观看 | 国产日韩一区二区三区在线观看 | 波多野结衣视频播放 | 日韩免费影院 | 在线免费成人网 | 国产不卡在线观看 | 国产精品综合久久久 | 日本免费一区视频 | 亚洲乱妇老熟女爽到高潮的片 | 亚洲激情视频网 | 成人在线国产精品 | 亚洲aⅴ| 亚洲精品你懂的 | 中文字幕制服诱惑 | 国产私人影院 | 亚洲福利影视 | 五月天色视频 | 人妻精品无码一区二区 | 人人草人人干 | 天天爽夜夜爽夜夜爽精品视频 | 久久久久久久福利 | 久久亚洲精精品中文字幕早川悠里 | 精品国产网 | 国产又粗又长又黄的视频 | a免费看| 最新最近中文字幕 | 丁香婷婷深情五月亚洲 | 亚洲深爱 | 中文字幕免费一区二区 | 91丨国产| 久久六六| 亚洲男人天堂2022 | 天天躁日日躁狠狠躁av麻豆男男 | 永久免费不卡在线观看黄网站 | 秋霞伦理一区二区 | 欧美日韩在线国产 | 日韩av综合网站 | av黄色免费在线观看 | 日韩激情文学 | 欧美精品三区 | 91原创视频在线观看 | 色综合色婷婷 | 国产午夜久久久 | 精品无码久久久久国产 | 国产欧美视频一区二区三区 | 精品人妻少妇一区二区三区 | 国产专区欧美专区 | 日本一区二区在线播放 | 欧美亚洲成人网 | 日本特黄一级大片 | 欧美精品黑人猛交高潮 | 草莓视频一区二区三区 | 日韩中文字幕在线观看视频 | 日本女人hd | 精品久久网 | 免费观看91视频 | 欧美无玛| 免费三片在线观看网站v888 | 91超级碰| 日韩精品一区中文字幕 | 精品无码一级毛片免费 | 人人干人| 老熟妇午夜毛片一区二区三区 | 反差在线观看免费版全集完整版 | 婷婷色站| 天天综合干| 天海翼av在线播放 | 伊人网大 | 五月婷婷综合久久 | 超碰人人爱人人 | 91网站大全 |