1. 程式人生 > >Spring Boot系列(十五) 安全框架Apache Shiro(二)快取-基於Hazelcast的分散式快取

Spring Boot系列(十五) 安全框架Apache Shiro(二)快取-基於Hazelcast的分散式快取

通常所說的“分散式”、“叢集服務”、“網格式記憶體資料”、“分散式快取“、“彈性可伸縮服務”這些非常牛逼高大上名詞拿到哪都是ITer裝逼的不二之選。在Javaer的世界,有這樣一個開源專案,只需要引入一個jar包、只需簡單的配置和編碼即可實現以上高階技能,他就是 Hazelcast。

在上一篇Shiro快取中,已經提到了,Shiro 1.3.x版本中,增加了一項非常重要,並且又很方便的功能,就是基於HazelCast的Session叢集(分散式快取)。當然,也可以使用比較流行的Redis作為快取,只是實現起來要複雜一點,使用Redis快取,會在後續章節中補充。

下面說一下Spring Boot中採用Hazelcast作為Shiro的分散式快取的案例。大家可以提前往後看一下Spring Boot 快取章節內容:

Spring Boot(十八)快取

當在pom中引入Hazelcast的jar包時,Spring Boot會自動初始化HazelcastInstance 和 HazelcastCacheManager,這時我們可以利用Spring自動載入的com.hazelcast.spring.cache.HazelcastCacheManager 來作為Shiro的快取(當然在ShiroConfiguration中直接初始化org.apache.shiro.hazelcast.cache.HazelcastCacheManager,但是這樣就啟動兩個Hazelcast節點),此時需要將spring的快取轉換為shiro的快取。具體實現步驟如下(基於Shiro Ehcache快取示例基礎上):

步驟1: 替換shiro包版本,移除ehcache快取的jar包,新增hazelcast相關jar包:

<!-- 使用Shiro認證 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>
org.apache.shiro</groupId> <artifactId>shiro-hazelcast</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-spring</artifactId> </dependency>

步驟2: 新增hazelcast.xml配置檔案,並在application.properties中指定路徑,如果不配置,則會使用hazelcast.jar包的hazelcast-default.xml檔案。主要配置(其他省略,拷貝過來修改即可,除下面列出的引數以外,其他引數會在後續單獨補充hazelcast的章節中說明)如下:

<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.6.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    ...
    <!-- 開啟連線hazelcast的管理中心,並指向管理中心url,預設是false-->
    <management-center enabled="true">http://localhost:8080/mancenter</management-center>
    ...
    <map name="default">
        ...
        <!-- 快取有效時間(如果不更新快取,快取有效的時間,超過時間會自動移除),預設0,無限時間 -->
        <time-to-live-seconds>120</time-to-live-seconds>
        <!-- 快取最大空閒時間(如果不訪問該快取,如果超過最大閒置時間,則自動移除),預設0,無限時間 -->
        <max-idle-seconds>120</max-idle-seconds>
        ...
    </map>
    ...
</hazelcast>
...
# hazelcast配置檔案,如果不指定,使用jar中預設的,
spring.cache.type=HAZELCAST
spring.cache.hazelcast.config=classpath:META-INF/hazelcast/hazelcast.xml
...

這裡說下為什麼配置map節點引數,因為shiro使用hazelcast的快取結構是 執行緒安全的Map介面ConcurrentMap,支援高併發。

步驟三:建立將 com.hazelcast.spring.cache.HazelcastCacheManager轉換為org.apache.shiro.hazelcast.cache.HazelcastCacheManager 的類:SpringCacheManagerWrapper

import java.util.Map;

import javax.annotation.Resource;

import org.apache.shiro.ShiroException;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MapCache;
import org.apache.shiro.util.Destroyable;
import org.apache.shiro.util.Initializable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.cache.HazelcastCacheManager;

public class SpringCacheManagerWrapper implements  CacheManager, Initializable, Destroyable {

    public static final Logger log = LoggerFactory.getLogger(SpringCacheManagerWrapper.class);
    @Resource
    private HazelcastCacheManager cacheManager;
    private boolean implicitlyCreated = false;
    private HazelcastInstance hazelcastInstance;
    private Config config;

    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        Map<K, V> map = hazelcastInstance.getMap(name); //returned map is a ConcurrentMap
        return new MapCache<K, V>(name, map);
    }
    protected HazelcastInstance ensureHazelcastInstance() {
        if (this.hazelcastInstance == null) {
            this.hazelcastInstance = createHazelcastInstance();
            this.implicitlyCreated = true;
        }
        return this.hazelcastInstance;
    }
    public void init() throws ShiroException {
        if(this.cacheManager == null){
            ensureHazelcastInstance();
        }else{
            hazelcastInstance = cacheManager.getHazelcastInstance();
        }
    }
    protected HazelcastInstance createHazelcastInstance() {
        return Hazelcast.newHazelcastInstance(this.config);
    }
    protected final boolean isImplicitlyCreated() {
        return this.implicitlyCreated;
    }
    public void destroy() throws Exception {
        if (this.implicitlyCreated) {
            try {
                this.hazelcastInstance.getLifecycleService().shutdown();
            } catch (Throwable t) {
                if (log.isWarnEnabled()) {
                    log.warn("Unable to cleanly shutdown implicitly created HazelcastInstance.  " +
                            "Ignoring (shutting down)...", t);
                }
            } finally {
                this.hazelcastInstance = null;
                this.implicitlyCreated = false;
            }
        }
    }
    public HazelcastInstance getHazelcastInstance() {
        return hazelcastInstance;
    }
    public void setHazelcastInstance(HazelcastInstance hazelcastInstance) {
        this.hazelcastInstance = hazelcastInstance;
    }
    public Config getConfig() {
        return config;
    }
    public void setConfig(Config config) {
        this.config = config;
    }
}

步驟4:在ShiroConfiguration中注入快取管理器(分析原始碼時,需要提前注入SessionManager,之後再注入快取管理器時,會嘗試向SessionManager中注入當前快取管理器)

@Bean
public SecurityManager securityManager() {
    logger.info("注入Shiro的Web過濾器-->securityManager", ShiroFilterFactoryBean.class);
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //設定Realm,用於獲取認證憑證
    securityManager.setRealm(userRealm());
    //注入session管理器
    securityManager.setSessionManager(sessionManager());
    //注入快取管理器
       securityManager.setCacheManager(hazelcastCacheManager());//這個如果執行多次,也是同樣的一個物件;

    return securityManager;
}
@Bean
public DefaultWebSessionManager sessionManager(){
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setSessionDAO(sessionDAO());
    return sessionManager;
}
/**
 * Shiro CacheManager 定義 Shiro 快取管理器
 * 
 * 需要加入到安全管理器:securityManager
 * @return
 */
@Bean
public SpringCacheManagerWrapper hazelcastCacheManager() {
    logger.info("注入Shiro的快取管理器-->hazelcastCacheManager", SpringCacheManagerWrapper.class);
    SpringCacheManagerWrapper cacheManager = new SpringCacheManagerWrapper();
    return cacheManager;
}

@Bean
public EnterpriseCacheSessionDAO sessionDAO(){
    EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
    return sessionDAO;
}

步驟5:啟動兩個應用,埠分別為8088和8089,可以看到啟動日誌:
啟動8089時日誌
啟動8088時日子
當啟動8089時,啟動了hazelcast的一個節點5701,當啟動8088時,啟動了hazelcast的第二個節點5702,並且這兩個節點已經實現互為備份的叢集關係。

步驟6:測試,通過在瀏覽器中開啟http://localhost:8080/mancenter 來監控hazelcast。(前提需要下載並執行mamcenter.war)
當登入後,可以看到Maps節點中shiro-activeSessionCache、authorizationCache、authenticationCache的快取情況:
Hazelcast 管理中心
由於設定了快取時間是2分鐘(120s),所以2分鐘內不重新整理瀏覽器會自動過期此時上圖畫紅線標註的session消失了(失效自動移除)。