Shiro與Redis整合,叢集下的session共享
概述
在叢集環境中,session共享一般通過應用伺服器的session複製或者儲存在公用的快取伺服器上,本文主要介紹通過Shiro管理session,並將session快取到redis中,這樣可以在叢集中使用。
Shiro除了在管理session上使用redis,也在可以快取使用者許可權,即cacheManager可以通過redis來擴充套件。
下面從cacheManager 和 sessionManager這兩部分講解Shiro與Redis的整合
spring-shiro.xml配置
<bean id="cacheManager" class="com.cnpc.framework.base.pojo.RedisCacheManager" />
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="adminRealm"/>
<property name="cacheManager" ref="cacheManager"/>
<!--將session託管給redis進行管理,便於搭建集群系統-->
<property name="sessionManager" ref="webSessionManager"/>
</bean>
<!--redis管理session-->
<bean id="webSessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="redisSessionDao"/>
<property name="deleteInvalidSessions" value ="true"/>
<property name="globalSessionTimeout" value="${shiro.session.timeout}"/>
<property name="sessionIdCookie" ref="sharesession"/>
<property name="sessionIdUrlRewritingEnabled" value="false"/>
<!-- 定時檢查失效的session -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
</bean>
<!-- sessionIdCookie的實現,用於重寫覆蓋容器預設的JSESSIONID -->
<bean id="sharesession" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的name,對應的預設是 JSESSIONID -->
<constructor-arg name="name" value="SHAREJSESSIONID"/>
<!-- jsessionId的path為 / 用於多個系統共享jsessionId -->
<property name="path" value="/"/>
</bean>
<bean id="redisSessionDao" class="com.cnpc.framework.base.pojo.RedisSessionDao">
<property name="expire" value="${shiro.session.timeout}"/>
</bean>
Shiro和Redis共同託管session
由Redis來管理session,包括session建立、讀取、刪除等,還可以統計線上使用者,下面是核心程式碼RedisSessionDao的實現:
package com.cnpc.framework.base.pojo;
import com.cnpc.framework.base.dao.RedisDao;
import com.cnpc.framework.base.entity.BaseEntity;
import com.cnpc.framework.constant.RedisConstant;
import com.cnpc.framework.utils.StrUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;
import javax.annotation.Resource;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by billJiang on 2017/4/13.
* e-mail:[email protected] qq:475572229
* 通過如下方式呼叫
* RedisSessionDao redisSession = (RedisSessionDao)SpringContextUtil.getBean("redisSessionDao");
*/
//@Service("redisSessionDao")
public class RedisSessionDao extends AbstractSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDao.class);
private long expire;
@Resource
private RedisDao redisDao;
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}
@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.error("session id is null");
return null;
}
logger.debug("Read Redis.SessionId=" + new String(getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, sessionId.toString())));
Session session = (Session) SerializationUtils.deserialize(redisDao.getByte(getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, sessionId.toString())));
return session;
}
@Override
public void update(Session session) throws UnknownSessionException {
this.saveSession(session);
}
int i=0;
public void saveSession(Session session) {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
session.setTimeout(expire);
long timeout = expire / 1000;
//儲存使用者會話
redisDao.add(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()), timeout, SerializationUtils.serialize(session));
//獲取使用者id
String uid = getUserId(session);
if (!StrUtil.isEmpty(uid)) {
//儲存使用者會話對應的UID
try {
redisDao.add(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()), timeout, uid.getBytes("UTF-8"));
//儲存線上UID
redisDao.add(this.getKey(RedisConstant.UID_PRE, uid), timeout, ("online"+(i++)+"").getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
logger.error("getBytes error:" + ex.getMessage());
}
}
}
public String getUserId(Session session) {
SimplePrincipalCollection pricipal = (SimplePrincipalCollection) session.getAttribute("org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY");
if (null != pricipal) {
return pricipal.getPrimaryPrincipal().toString();
}
return null;
}
public String getKey(String prefix, String keyStr) {
return prefix + keyStr;
}
@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
//刪除使用者會話
redisDao.delete(this.getKey(RedisConstant.SHIRO_REDIS_SESSION_PRE, session.getId().toString()));
//獲取快取的使用者會話對應的UID
String uid = redisDao.get(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
//刪除使用者會話sessionid對應的uid
redisDao.delete(this.getKey(RedisConstant.SHIRO_SESSION_PRE, session.getId().toString()));
//刪除線上uid
redisDao.delete(this.getKey(RedisConstant.UID_PRE, uid));
//刪除使用者快取的角色
redisDao.delete(this.getKey(RedisConstant.ROLE_PRE, uid));
//刪除使用者快取的許可權
redisDao.delete(this.getKey(RedisConstant.PERMISSION_PRE, uid));
}
@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<>();
Set<String> keys = redisDao.keys(RedisConstant.SHIRO_REDIS_SESSION_PRE + "*");
if (keys != null && keys.size() > 0) {
for (String key : keys) {
Session s = (Session) SerializationUtils.deserialize(redisDao.getByte(key));
sessions.add(s);
}
}
return sessions;
}
/**
* 當前使用者是否線上
*
* @param uid 使用者id
* @return
*/
public boolean isOnLine(String uid) {
Set<String> keys = redisDao.keys(RedisConstant.UID_PRE + uid);
if (keys != null && keys.size() > 0) {
return true;
}
return false;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
}
上述的redisDao程式碼的實現參考我的上篇部落格Spring整合Redis步驟。
Redis快取使用者許可權
在realm的doGetAuthorizationInfo
中,獲取的SimpleAuthorizationInfo authorizationInfo
會存在快取中,本文也用Redis進行快取;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 因為非正常退出,即沒有顯式呼叫 SecurityUtils.getSubject().logout()
// (可能是關閉瀏覽器,或超時),但此時快取依舊存在(principals),所以會自己跑到授權方法裡。
if (!SecurityUtils.getSubject().isAuthenticated()) {
doClearCache(principals);
SecurityUtils.getSubject().logout();
return null;
}
if (principals == null) {
throw new AuthorizationException("parameters principals is null");
}
//獲取已認證的使用者名稱(登入名)
String userId=(String)super.getAvailablePrincipal(principals);
if(StrUtil.isEmpty(userId)){
return null;
}
Set<String> roleCodes=roleService.getRoleCodeSet(userId);
//預設使用者擁有所有許可權
Set<String> functionCodes=functionService.getAllFunctionCode();
/* Set<String> functionCodes=functionService.getFunctionCodeSet(roleCodes);*/
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roleCodes);
authorizationInfo.setStringPermissions(functionCodes);
return authorizationInfo;
}
RedisCacheManager.java實現
package com.cnpc.framework.base.pojo;
import com.cnpc.framework.base.dao.RedisDao;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Created by billJiang on 2017/4/15.
* e-mail:[email protected] qq:475572229
*/
public class RedisCacheManager implements CacheManager {
private static final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);
// fast lookup by name map
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();
@Resource
private RedisDao redisDao;
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("獲取名稱為: " + name + " 的RedisCache例項");
Cache c = caches.get(name);
if (c == null) {
c = new RedisCache<K, V>(redisDao);
caches.put(name, c);
}
return c;
}
}
RedisCache.java實現
package com.cnpc.framework.base.pojo;
import com.cnpc.framework.base.dao.RedisDao;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.SerializationUtils;
import java.util.*;
/**
* Created by billJiang on 2017/4/15.
* e-mail:[email protected] qq:475572229
*/
public class RedisCache<K, V> implements Cache<K, V> {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* The wrapped Jedis instance.
*/
//private RedisManager redisDao;
private RedisDao redisDao;
/**
* The Redis key prefix for the sessions
*/
private String keyPrefix = "shiro_redis_session:";
/**
* Returns the Redis session keys
* prefix.
*
* @return The prefix
*/
public String getKeyPrefix() {
return keyPrefix;
}
/**
* Sets the Redis sessions key
* prefix.
*
* @param keyPrefix The prefix
*/
public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/**
* 通過一個JedisManager例項構造RedisCache
*/
public RedisCache(RedisDao redisDao) {
if (redisDao == null) {
throw new IllegalArgumentException("Cache argument cannot be null.");
}
this.redisDao = redisDao;
}
/**
* Constructs a redisDao instance with the specified
* Redis manager and using a custom key prefix.
*
* @param redisDao The redisDao manager instance
* @param prefix The Redis key prefix
*/
public RedisCache(RedisDao redisDao,
String prefix) {
this(redisDao);
// set the prefix
this.keyPrefix = prefix;
}
/**
* 獲得byte[]型的key
*
* @param key
* @return
*/
private byte[] getByteKey(K key) {
if (key instanceof String) {
String preKey = this.keyPrefix + key;
return preKey.getBytes();
} else {
return SerializationUtils.serialize(key);
}
}
@Override
public V get(K key) throws CacheException {
logger.debug("根據key從Redis中獲取物件 key [" + key + "]");
try {
if (key == null) {
return null;
} else {
byte[] rawValue = redisDao.getByte(key.toString());
@SuppressWarnings("unchecked")
V value = (V) SerializationUtils.deserialize(rawValue);
return value;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public V put(K key, V value) throws CacheException {
logger.debug("根據key從儲存 key [" + key + "]");
try {
redisDao.set(key.toString(), SerializationUtils.serialize(value));
return value;
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public V remove(K key) throws CacheException {
logger.debug("從redis中刪除 key [" + key + "]");
try {
V previous = get(key);
redisDao.delete(key.toString());
return previous;
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public void clear() throws CacheException {
logger.debug("從redis中刪除所有元素");
try {
redisDao.flushDB();
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public int size() {
try {
Long longSize = new Long(redisDao.dbSize());
return longSize.intValue();
} catch (Throwable t) {
throw new CacheException(t);
}
}
@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
try {
Set<String> keys = redisDao.keys(this.keyPrefix + "*");
if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
} else {
Set<K> newKeys = new HashSet<K>();
for (String key : keys) {
newKeys.add((K) key);
}
return newKeys;
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
@Override
public Collection<V> values() {
try {
Set<String> keys = redisDao.keys(this.keyPrefix + "*");
if (!CollectionUtils.isEmpty(keys)) {
List<V> values = new ArrayList<V>(keys.size());
for (String key : keys) {
@SuppressWarnings("unchecked")
V value = get((K) key);
if (value != null) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
} else {
return Collections.emptyList();
}
} catch (Throwable t) {
throw new CacheException(t);
}
}
}
總結
通過以上程式碼實現了Shiro和Redis的整合,Redis快取了使用者session和許可權資訊authorizationInfo。
相關推薦
Shiro與Redis整合,叢集下的session共享
概述 在叢集環境中,session共享一般通過應用伺服器的session複製或者儲存在公用的快取伺服器上,本文主要介紹通過Shiro管理session,並將session快取到redis中,這樣可以在叢集中使用。 Shiro除了在管理session上使用re
記一次與Shiro有關的錯誤,404導致session丟失需要重新登入
一 問題描述 前段時間上司突然叫我幫忙解決老專案上的一個bug,出現的問題是不同使用者賬號,進入同一個頁面,有個別用戶重新整理一下當前頁面就會重定向到登入頁面,需要重新登入。 這是一個幾年前的一個專案,使用的是Srping + Spring MVC + Shiro + Jsp的專案,之前沒用過Shiro,
18.Shiro與Springboot整合下登陸驗證UserService未注入的問題
Shiro與Springboot整合下登陸驗證UserService未注入的問題 前言: 剛開始整合的情況下,UserService一執行,就會報空指標異常。 看了網上各位大神的講解,什麼不能用service層,直接用dao層獲取。。。。。。 然後跟著一路再坑。。。。。。。 最後的最後,才發現MyR
spring 和 redis整合,並且使用redis做session快取伺服器
所需jar commons-pool2-2.0.jar jedis-2.9.0.jar spring-data-redis-1.6.2.RELEASE.jar spring-session-1.2.1.RELEASE.jar 一 . xml配
使用shiro和redis結合,管理SessionDAO的對Session的CRUD,並原始碼分析
SessionDAO的作用是為Session提供CRUD並進行持久化的一個shiro元件,將整合redis快取進行開發 由配置檔案可以知道sessionManager需要注入一個sessionDao <!-- 自定義會話管理配置 --> <bean i
Shiro許可權管理框架(二):Shiro結合Redis實現分散式環境下的Session共享
首發地址:https://www.guitu18.com/post/2019/07/28/44.html 本篇是Shiro系列第二篇,使用Shiro基於Redis實現分散式環境下的Session共享。在講Session共享之前先說一下為什麼要做Session共享。 為什麼要做Session共享 什麼是Ses
樹形ztree 與angularjs結合,實現下級數據異步加載,點擊復選框 填寫到輸入框裏
沒有 hide deb out IV UNC -s parent default html:<input value="" type="text" id="river_cut" onclick="
(簡)樹形ztree 與angularjs結合,實現下級數據,點擊復選框 填寫到輸入框裏
url let 輸入 樹形 fadeout ros mar 分隔符 3.4 html:<link href="vendors/zTreeStyle/zTreeStyle.css" rel="stylesheet" />生態
SpringBoot框架與MyBatis整合,連線Mysql資料庫
SpringBoot是一種用來簡化新Spring應用初始搭建及開發過程的框架,它使用特定方式來進行配置,使得開發人員不再需要定義樣板化的配置。MyBatis是一個支援普通SQL查詢、儲存和高階對映的持久層框架,它消除了幾乎所有的JDBC程式碼和引數的手工配置以及對結果集的檢索封裝,可以使用簡單的XML或註
ssm中加入redis,spring -redis整合,簡單,認識redis
1匯入spring和redis的jar包,當然推薦用maven。開啟服務 2.application.xml中配置 定義執行緒池 <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
Shiro使用Redis作儲存之後更新Session失敗的問題
問題 因為想在多個應用之間共享使用者的登入態,因此實現了自己的SessionDAO,使用Kryo把SimpleSession序列化然後放到redis之中去,同時也使用了shiro.userNativeSessionManager: true來使用shiro自己的儲存。然而之後一直出現丟失更新的問題,例如
Ribbon 框架簡介及搭建(沒有與SpringCloud整合,獨立使用)
Ribbon簡介 1. 負載均衡框架,支援可插拔式的負載均衡規則 2. 支援多種協議,如HTTP、UDP等 3. 提供負載均衡客戶端 Ribbon子模組 1. ribbon-core(ribbon的核心,主要包含負載均衡器、負載均衡介面、客戶端介面
springboot專案,layui與pageHelper整合,列表分頁,條件查詢
一、前端頁面 重點:1、table.render初始化載入資料 2、reload查詢按鈕觸發,資料重新載入
SpringBoot與MyBatis整合,底層資料庫為mysql的使用示例
專案下載連結:https://github.com/DFX339/bootdemo.git 新建maven專案,web專案,專案名為 bootdemo 專案結構目錄如下:還有個pom.xml檔案沒有在截圖裡面 專案需要編寫的檔案主
7、Spring Boot 與 Redis 整合
1.7 Spring Boot 與 Redis 整合 簡介 繼續上篇的MyBatis操作,詳細介紹在Spring Boot中使用RedisCacheManager作為快取管理器,整合業務於一體。 完整原始碼: Spring-Boot-Demos 1.7.1 建立 spring-boot-r
Springboot中Spring-cache與redis整合
也是在整合redis的時候偶然間發現spring-cache的。這也是一個不錯的框架,與spring的事務使用類似,只要新增一些註解方法,就可以動態的去操作快取了,減少程式碼的操作。如果這些註解不滿足專案的需求,我們也可以參考spring-cache的實現思想,使用AOP代理+快取操作來管理快取的使
spring 與 redis 整合及使用
1、實現目標 通過redis快取資料。(目的不是加快查詢的速度,而是減少資料庫的負擔) 2、所需jar包 注意:jdies和commons-pool兩個jar的版本是有對應關係的,注意引入jar包是要配對使用,否則將會報錯。因為commons-pooljar的目錄根據
apache+Tomcat叢集下session複製
原地址:https://blog.csdn.net/weiweiai123456/article/details/41750887 因為工作需要,本人需要在本機上做一個apache負載均衡Http請求,交給Tomcat叢集處理,並且Session要在不同的Tomcat上進行復制的D
Redis叢集+tomcat7叢集實現session共享
就在昨天,一個線上的產品突然不能訪問了,經過一系列的排查問題,最終發現原來是redis死掉了,因為做的用redis管理session,redis又是單臺伺服器,當redis宕機後,網站就訪問不上了。為了避免以後redis宕機導致網站上不去,同時也為了網站的穩健性,我決定把re
Springboot 與 Redis 整合 簡易Redis工具類實現
最近專案需要處理一項資料量比較大的業務,考慮之下,高頻訪問/讀取決定使用Redis.自己的Springboot框架下研究了Redis兩天,把成果總結一下 開發環境介紹 JDK1.7 Redis 基礎依賴 org.mybatis.spring.boot myba