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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

redisson集成spring-session和shiro实现分布式session

發布時間:2025/3/15 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redisson集成spring-session和shiro实现分布式session 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、pom

<!-- Shiro權限驗證 --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.6.0</version></dependency><!-- 使用Shiro<Tag>--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><!--redisson--><!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter --><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.16.3</version></dependency><!--session-redis--><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency>

二、使用redisson的方式實現shiro的cache和cachemanager

  • RedissonShiroCacheManager
@Component public class RedissonShiroCacheManager implements CacheManager, Initializable {private boolean allowNullValues = true;private Codec codec = new JsonJacksonCodec();private RedissonClient redisson;private String configLocation;private Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();private ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();public RedissonShiroCacheManager(){}public RedissonShiroCacheManager(RedissonClient redisson){this(redisson, (String)null, null);}public RedissonShiroCacheManager(RedissonClient redisson, Map<String, ? extends CacheConfig> config) {this(redisson, config, null);}public RedissonShiroCacheManager(RedissonClient redisson, Map<String, ? extends CacheConfig> config, Codec codec) {this.redisson = redisson;this.configMap = (Map<String, CacheConfig>) config;if (codec != null) {this.codec = codec;}}public RedissonShiroCacheManager(RedissonClient redisson, String configLocation) {this(redisson, configLocation, null);}public RedissonShiroCacheManager(RedissonClient redisson, String configLocation, Codec codec) {this.redisson = redisson;this.configLocation = configLocation;if (codec != null) {this.codec = codec;}}protected CacheConfig createDefaultConfig() {return new CacheConfig();}@Overridepublic <K, V> Cache<K, V> getCache(String name) throws CacheException {Cache<K, V> cache = this.instanceMap.get(name);if (cache != null) {return cache;}CacheConfig config = this.configMap.get(name);if (config == null) {config = createDefaultConfig();configMap.put(name, config);}if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {return createMap(name, config);}return createMapCache(name, config);}private <K, V> Cache<K, V> createMap(String name, CacheConfig config) {RMap<K, Object> map = getMap(name, config);Cache<K, V> cache = new RedissonShiroCache<>(map, this.allowNullValues);Cache<K, V> oldCache = this.instanceMap.putIfAbsent(name, cache);if (oldCache != null) {cache = oldCache;}return cache;}protected <K> RMap<K, Object> getMap(String name, CacheConfig config) {if (this.codec != null) {return this.redisson.getMap(name, this.codec);}return this.redisson.getMap(name);}private <K, V> Cache<K, V> createMapCache(String name, CacheConfig config) {RMapCache<K, Object> map = getMapCache(name, config);Cache<K, V> cache = new RedissonShiroCache<>(map, config, this.allowNullValues);Cache<K, V> oldCache = this.instanceMap.putIfAbsent(name, cache);if (oldCache != null) {cache = oldCache;} else {map.setMaxSize(config.getMaxSize());}return cache;}protected <K> RMapCache<K, Object> getMapCache(String name, CacheConfig config) {if (this.codec != null) {return this.redisson.getMapCache(name, this.codec);}return redisson.getMapCache(name);}@Overridepublic void init() {if (this.configLocation == null) {return;}try {this.configMap = (Map<String, CacheConfig>) CacheConfig.fromJSON(ResourceUtils.getInputStreamForPath(this.configLocation));} catch (IOException e) {// try to read yamltry {this.configMap = (Map<String, CacheConfig>) CacheConfig.fromYAML(ResourceUtils.getInputStreamForPath(this.configLocation));} catch (IOException e1) {throw new IllegalArgumentException("Could not parse cache configuration at [" + configLocation + "]", e1);}}}public void setConfig(Map<String, ? extends CacheConfig> config) {this.configMap = (Map<String, CacheConfig>) config;}public RedissonClient getRedisson() {return redisson;}public void setRedisson(RedissonClient redisson) {this.redisson = redisson;}public Codec getCodec() {return codec;}public void setCodec(Codec codec) {this.codec = codec;}public String getConfigLocation() {return configLocation;}public void setConfigLocation(String configLocation) {this.configLocation = configLocation;}public boolean isAllowNullValues() {return allowNullValues;}public void setAllowNullValues(boolean allowNullValues) {this.allowNullValues = allowNullValues;} }
  • RedissonShiroCache
public class RedissonShiroCache<K, V> implements Cache<K, V> {private RMapCache<K, Object> mapCache;private final RMap<K, Object> map;private CacheConfig config;private final boolean allowNullValues;private final AtomicLong hits = new AtomicLong();private final AtomicLong misses = new AtomicLong();public RedissonShiroCache(RMapCache<K, Object> mapCache, CacheConfig config, boolean allowNullValues) {this.mapCache = mapCache;this.map = mapCache;this.config = config;this.allowNullValues = allowNullValues;}public RedissonShiroCache(RMap<K, Object> map, boolean allowNullValues) {this.map = map;this.allowNullValues = allowNullValues;}@Overridepublic V get(K key) throws CacheException {Object value = this.map.get(key);if (value == null) {addCacheMiss();} else {addCacheHit();}return fromStoreValue(value);}@Overridepublic V put(K key, V value) throws CacheException {Object previous;if (!allowNullValues && value == null) {if (mapCache != null) {previous = mapCache.remove(key);} else {previous = map.remove(key);}return fromStoreValue(previous);}Object val = toStoreValue(value);if (mapCache != null) {previous = mapCache.put(key, val, config.getTTL(), TimeUnit.MILLISECONDS,config.getMaxIdleTime(), TimeUnit.MILLISECONDS);} else {previous = map.put(key, val);}return fromStoreValue(previous);}public void fastPut(K key, V value) throws CacheException {if (!allowNullValues && value == null) {if (mapCache != null) {mapCache.fastRemove(key);} else {map.fastRemove(key);}return;}Object val = toStoreValue(value);if (mapCache != null) {mapCache.fastPut(key, val, config.getTTL(), TimeUnit.MILLISECONDS,config.getMaxIdleTime(), TimeUnit.MILLISECONDS);} else {map.fastPut(key, val);}}public V putIfAbsent(K key, V value) throws CacheException {Object previous;if (!allowNullValues && value == null) {if (mapCache != null) {previous = mapCache.get(key);} else {previous = map.get(key);}return fromStoreValue(previous);}Object val = toStoreValue(value);if (mapCache != null) {previous = mapCache.putIfAbsent(key, val, config.getTTL(), TimeUnit.MILLISECONDS,config.getMaxIdleTime(), TimeUnit.MILLISECONDS);} else {previous = map.putIfAbsent(key, val);}return fromStoreValue(previous);}public boolean fastPutIfAbsent(K key, V value) throws CacheException {if (!allowNullValues && value == null) {return false;}Object val = toStoreValue(value);if (mapCache != null) {return mapCache.fastPutIfAbsent(key, val, config.getTTL(), TimeUnit.MILLISECONDS,config.getMaxIdleTime(), TimeUnit.MILLISECONDS);} else {return map.fastPutIfAbsent(key, val);}}@Overridepublic V remove(K key) throws CacheException {Object previous = this.map.remove(key);return fromStoreValue(previous);}public long fastRemove(K ... keys) {return this.map.fastRemove(keys);}@Overridepublic void clear() throws CacheException {this.map.clear();}@Overridepublic int size() {return this.map.size();}@Overridepublic Set<K> keys() {return this.map.readAllKeySet();}@Overridepublic Collection<V> values() {Collection<Object> innerValues = this.map.readAllValues();Collection<V> res = new ArrayList<>(innerValues.size());for (Object val : innerValues) {res.add(fromStoreValue(val));}return res;}protected V fromStoreValue(Object storeValue) {if (storeValue instanceof NullValue) {return null;}return (V) storeValue;}protected Object toStoreValue(V userValue) {if (userValue == null) {return NullValue.INSTANCE;}return userValue;}/** The number of get requests that were satisfied by the cache.* @return the number of hits*/long getCacheHits(){return this.hits.get();}/** A miss is a get request that is not satisfied.* @return the number of misses*/long getCacheMisses(){return this.misses.get();}private void addCacheHit(){this.hits.incrementAndGet();}private void addCacheMiss(){this.misses.incrementAndGet();} }

三、開啟springsession,接管shiro-session

@SpringBootApplication //使用EnableRedisHttpSession注解開啟spring分布式session,該類的作用是配置org.springframework.session.web.http.SessionRepositoryFilter進行請求攔截 @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600) @EnableCaching public class GeApplication {public static void main(String[] args) {SpringApplication.run(GeApplication.class, args);} }
  • shiroconfig類的配置
public class ShiroConfig {@AutowiredRedissonClient redissonClient;/*** ServletContainerSessionManager 類中有一個方法是isServletContainerSessions(),返回的是true.* DefaultWebSessionManager類中有一個方法是isServletContainerSessions(),返回是false。* 因為實現了Spring Session,代理了所有Servlet里的session,所以這里的session一定是Servlet能控制的,否則無法實現Spring session共享。*/@Beanpublic SessionManager sessionManager(){return new ServletContainerSessionManager();}/*** 注入這個是是為了在thymeleaf中使用shiro的自定義tag。*/@Bean(name = "shiroDialect")public ShiroDialect shiroDialect() {return new ShiroDialect();}/*** 地址過濾器*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 設置securityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 設置登錄urlshiroFilterFactoryBean.setLoginUrl("/login");// 設置主頁urlshiroFilterFactoryBean.setSuccessUrl("/shiro");// 設置未授權的urlshiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 注銷登錄LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();filtersMap.put("logout", shiroLogoutFilter());shiroFilterFactoryBean.setFilters(filtersMap);filterChainDefinitionMap.put("/loginOut", "logout");// 開放登錄接口filterChainDefinitionMap.put("/doLogin", "anon");........// 其余url全部攔截,必須放在最后filterChainDefinitionMap.put("/**", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}public ShiroLogoutFilter shiroLogoutFilter(){ShiroLogoutFilter shiroLogoutFilter = new ShiroLogoutFilter(redissonClient);//配置登出后重定向的地址,等出后配置跳轉到登錄接口shiroLogoutFilter.setRedirectUrl("/login");return shiroLogoutFilter;}@Bean("authenticator")public SessionsSecurityManager securityManager() {SessionsSecurityManager securityManager = new DefaultWebSecurityManager();//設置認證realmsecurityManager.setRealm(loginRealm());// 設置記住我功能 // securityManager.setRememberMeManager(rememberMeManager());// 設置會話管理器securityManager.setSessionManager(sessionManager());return securityManager;}@Beanpublic RememberMeManager rememberMeManager() {CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();//注入自定義cookie(主要是設置壽命, 默認的一年太長)SimpleCookie simpleCookie = new SimpleCookie("rememberMe");simpleCookie.setHttpOnly(true);//設置RememberMe的cookie有效期為7天simpleCookie.setMaxAge(604800);rememberMeManager.setCookie(simpleCookie);//手動設置對稱加密秘鑰,防止重啟系統后系統生成新的隨機秘鑰,防止導致客戶端cookie無效rememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA=="));return rememberMeManager;}@Beanpublic LoginRealm loginRealm() {LoginRealm loginRealm = new LoginRealm();//開啟緩存處理loginRealm.setCacheManager(new RedissonShiroCacheManager(redissonClient));//開啟全局緩存//loginRealm.setCachingEnabled(true); // //開啟認證緩存 // loginRelam.setAuthenticationCachingEnabled(true);//loginRealm.setAuthenticationCacheName("authenticafdfdfdtionCache");//認證授權緩存 // loginRealm.setAuthorizationCachingEnabled(true); // loginRealm.setAuthorizationCacheName("AuthorizationCache");return loginRealm;}/*** 以下是為了能夠使用@RequiresPermission()等標簽* 這里命名為advisorAutoProxyCreatorShiro是因為advisorAutoProxyCreator會與druid沖突*/@Bean@DependsOn({"lifecycleBeanPostProcessor"})public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreatorShiro() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}/*** LifecycleBeanPostProcessor將Initializable和Destroyable的實現類統一在其內部* 自動分別調用了Initializable.init()和Destroyable.destroy()方法,從而達到管理shiro bean生命周期的目的。*/@Beanpublic static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());return authorizationAttributeSourceAdvisor;} }

四、自定義shiro的登出行為,做一些springsession中與用戶有關的緩存操作

  • 自定義shiro只需要實現LogoutFilter,重寫prehandle方法即可
public class ShiroLogoutFilter extends LogoutFilter {private final RedissonClient redissonClient;/*** @param request* @param response* @throws Exception*/@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {Subject subject = getSubject(request,response);try {HttpServletRequest httpReq = (HttpServletRequest) request;HttpSession session = httpReq.getSession();//根據spring session的信息,刪除用戶的緩存String sessionKey="spring:session:sessions:" + session.getId();String expires="spring:session:sessions:expires:" + session.getId();redissonClient.getBucket(sessionKey).delete();redissonClient.getBucket(expires).delete();} catch (Throwable t) {t.printStackTrace();}//登出subject.logout();//獲取登出后重定向到的地址String redirectUrl = getRedirectUrl(request,response,subject);//重定向issueRedirect(request,response,redirectUrl);//配置登出后重定向的地址,等出后配置跳轉到登錄接口return false;}public ShiroLogoutFilter(RedissonClient redissonClient){this.redissonClient=redissonClient;} }

五、最后,redissonnclient用于操作redis

@Configuration public class RedissonConfig {@Bean(destroyMethod="shutdown")public RedissonClient getRedissonClient() throws IOException {ResourceLoader loader = new DefaultResourceLoader();Resource resource = loader.getResource("redisson.yml");Config config = Config.fromYAML(resource.getInputStream());return Redisson.create(config);} }
  • redisson.yml
singleServerConfig:idleConnectionTimeout: 10000 #連接空閑超時,單位:毫秒 默認:10000connectTimeout: 10000 #連接超時,單位:毫秒。默認:10000timeout: 3000 #命令等待超時,單位:毫秒 默認:3000retryAttempts: 3 #命令失敗重試次數retryInterval: 1500 #命令重試發送時間間隔,單位:毫秒# lockWatchdogTimeout: 30000 #監控鎖的看門狗超時,單位:毫秒 該參數只適用于分布式鎖的加鎖請求中未明確使用leaseTimeout參數的情況。如果該看門口未使用lockWatchdogTimeout去重新調整一個分布式鎖的lockWatchdogTimeout超時,那么這個鎖將變為失效狀態。這個參數可以用來避免由Redisson客戶端節點宕機或其他原因造成死鎖的情況。password: xxxxxxxxxclientName: null #客戶端名稱subscriptionsPerConnection: 5 #單個連接最大訂閱數量address: "redis://ip:port"subscriptionConnectionMinimumIdleSize: 1 #從節點發布和訂閱連接的最小空閑連接數subscriptionConnectionPoolSize: 50 #從節點發布和訂閱連接池大小# 集群模式下不支持該選項database: 10dnsMonitoringInterval: 5000 #DNS監控間隔,單位:毫秒 在啟用該功能以后,Redisson將會監測DNS的變化情況sslEnableEndpointIdentification: true #啟用SSL終端識別,默認為true threads: 0 #線程池數量 默認值: 當前處理核數量 * 2 nettyThreads: 0 #Netty線程池數量 默認值: 當前處理核數量 * 2 ,這個線程池數量是在一個Redisson實例內,被其創建的所有分布式數據類型和服務,以及底層客戶端所一同共享的線程池里保存的線程數量。 codec: #編碼 默認值:org.redisson.codec.JsonJacksonCodec,Redisson的對象編碼類是用于將對象進行序列化和反序列化,以實現對該對象在Redis里的讀取和存儲!<org.redisson.codec.JsonJacksonCodec> {} "transportMode": #傳輸模式 默認:TransportMode.NIO linux系統下可以使用RPOLL,性能高"NIO"

六、總結

通過spring-session集成shiro,我們可以實現用戶權限控制,認證緩存,將session的存儲位置由tomcat等web容器剝離至redis或者mysql中進行持久化,這樣即使微服務中某臺機器宕機,重啟,也不會丟失用戶信息,進一步提高系統的健壯性。同時,本例子只在在單機redis的情況下使用,各位可以進一步將redis擴展到redis集群,在redisson的幫助下,使用起來也非常方便

總結

以上是生活随笔為你收集整理的redisson集成spring-session和shiro实现分布式session的全部內容,希望文章能夠幫你解決所遇到的問題。

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