上一篇已經解決了第一個問題,session的共用,現在我們解決第二個問題cache的共用。
先看下spring的設定檔,上一篇已經提到過了
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="userRepository,roleRepository"><property name="sessionManager" ref="defaultWebSessionManager" /><property name="realm" ref="shiroDbRealm" /><property name="cacheManager" ref="memoryConstrainedCacheManager" /></bean><bean id="memoryConstrainedCacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
這裡cacheManager我們注入了shiro自定的本機記憶體實現的cacheManager類,當然,這肯定不滿足我們叢集的需要,所以我們要自己實現cacheManager類,這裡我還是用了redis作為cache的儲存,先建立CustomShiroCacheManager實作類別
public class CustomShiroCacheManager implements CacheManager, Destroyable {private ShiroCacheManager shiroCacheManager;public ShiroCacheManager getShiroCacheManager() {return shiroCacheManager;}public void setShiroCacheManager(ShiroCacheManager shiroCacheManager) {this.shiroCacheManager = shiroCacheManager;}@Overridepublic <K, V> Cache<K, V> getCache(String name) throws CacheException {return getShiroCacheManager().getCache(name);}@Overridepublic void destroy() throws Exception {shiroCacheManager.destroy();}}
這裡為了擴充,引入了ShiroCacheManager介面
public interface ShiroCacheManager {<K, V> Cache<K, V> getCache(String name);void destroy();}下面我們自己實現redis的cacheManger
public class JedisShiroCacheManager implements ShiroCacheManager {@Autowiredprivate JedisCacheManager jedisCacheManager;@Overridepublic <K, V> Cache<K, V> getCache(String name) {return new JedisShiroCache<K, V>(name, jedisCacheManager);}@Overridepublic void destroy() {jedisCacheManager.getJedis().shutdown();}}
當然,這裡僅僅是getCache,我第一次看源碼時,也有這樣的疑問,cache的add、remove等等方法在哪裡實現呢。我們繼續看看shiro的源碼就會知道答案了
這個是自己relm的AuthorizingRealm中的方法
protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { return null; } AuthorizationInfo info = null; if (log.isTraceEnabled()) { log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]"); } Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache(); if (cache != null) { if (log.isTraceEnabled()) { log.trace("Attempting to retrieve the AuthorizationInfo from cache."); } Object key = getAuthorizationCacheKey(principals); info = cache.get(key); if (log.isTraceEnabled()) { if (info == null) { log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]"); } else { log.trace("AuthorizationInfo found in cache for principals [" + principals + "]"); } } } if (info == null) { // Call template method if the info was not found in a cache info = <strong>doGetAuthorizationInfo</strong>(principals); // If the info is not null and the cache has been created, then cache the authorization info. if (info != null && cache != null) { if (log.isTraceEnabled()) { log.trace("Caching authorization info for principals: [" + principals + "]."); } Object key = getAuthorizationCacheKey(principals); <strong>cache.put</strong>(key, info); } } return info; }
如果大家去查查引入就知道我們自訂relm要實現一個叫做doGetAuthorizationInfo()的方法,它的作用是查詢授權資訊,我們注意加粗的2行,發現其實cache的put方法其實才是cache儲存的核心類,其實現都在cache中,所以我們需要實現自己的cache,建立JedisShiroCache類
public class JedisShiroCache<K, V> implements Cache<K, V> {private final String REDIS_SHIRO_CACHE = "shiro-cache:";private JedisManager jedisManager;private String name;public JedisShiroCache(String name, JedisManager jedisManager) {this.name = name;this.jedisManager = jedisManager;}/** * 自訂relm中的授權/認證的類名加上授權/認證英文名字 * @return */public String getName() {if (name == null)return "";return name;}public void setName(String name) {this.name = name;}@Overridepublic V get(K key) throws CacheException {byte[] byteKey = SerializeUtil.serialize(getCacheKey(key));byte[] byteValue = jedisManager.getValueByKey(byteKey);return (V) SerializeUtil.deserialize(byteValue);}@Overridepublic V put(K key, V value) throws CacheException {V previos = get(key);jedisManager.saveValueByKey(SerializeUtil.serialize(getCacheKey(key)),SerializeUtil.serialize(value));return previos;}@Overridepublic V remove(K key) throws CacheException {V previos = get(key);jedisManager.deleteByKey(SerializeUtil.serialize(getCacheKey(key)));return previos;}@Overridepublic void clear() throws CacheException {byte[] keysPattern = SerializeUtil.serialize(this.REDIS_SHIRO_CACHE+ "*");jedisManager.deleteByKeysPattern(keysPattern);}@Overridepublic int size() {if (keys() == null)return 0;return keys().size();}@Overridepublic Set<K> keys() {Set<byte[]> byteSet = jedisManager.getKeysByKeysPattern(SerializeUtil.serialize(this.REDIS_SHIRO_CACHE + "*"));Set<K> keys = new HashSet<K>();for (byte[] bs : byteSet) {keys.add((K) SerializeUtil.deserialize(bs));}return keys;}@Overridepublic Collection<V> values() {Set<byte[]> byteSet = jedisManager.getKeysByKeysPattern(SerializeUtil.serialize(this.REDIS_SHIRO_CACHE + "*"));List<V> result = new LinkedList<V>();for (byte[] bs : byteSet) {result.add((V) SerializeUtil.deserialize(jedisManager.getValueByKey(bs)));}return result;}private String getCacheKey(Object key) {return this.REDIS_SHIRO_CACHE + getName() + ":" + key;}
最後修改spring設定檔
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" depends-on="userRepository,roleRepository"> <property name="sessionManager" ref="defaultWebSessionManager" /><property name="realm" ref="shiroDbRealm" /><property name="cacheManager" ref="customShiroCacheManager" /></bean><bean id="customShiroCacheManager" class="com.nfschina.fourjoy.security.shiro.custom.cache.CustomShiroCacheManager"><property name="shiroCacheManager" ref="jedisShiroCacheManager" /></bean><bean id="jedisShiroCacheManager" class="com.nfschina.fourjoy.security.shiro.custom.cache.JedisShiroCacheManager" />
這樣就完成了整個shiro叢集的配置
源碼地址:https://github.com/michaelliuyang/shiro-redis-cluster