apache shiro整合redis緩衝

來源:互聯網
上載者:User

項目中經常使用shiro做許可權認證與授權功能,當使用者認證成功後,第一次訪問受限的資源時,shiro會去載入使用者能訪問的所有許可權標識。預設情況下,shiro並未緩衝這些許可權標識。當再次訪問受限的資源時,還會去載入使用者能訪問的許可權標識。
當請求多時,這樣處理顯然不適合生產環境,因此需要為shiro加緩衝。shiro本身內建有緩衝功能,需要配置啟用它。shiro為我們提供了兩個緩衝實現,一個是基於本地記憶體(org.apache.shiro.cache.MemoryConstrainedCacheManager),另一個是基於EhCache(org.apache.shiro.cache.ehcache.EhCacheManager)。這兩套實現都只適合單機玩,當在分布式環境下效果就不理想了。於是經過研究,研發了一套基於redis的shiro緩衝實現。
以下不介紹spring與shiro的整合,此類文章網上應有盡有,不是本文關注的重點,因此需要讀者事先已將spring與shiro整合,再進行本文的實踐。同時也不介紹spring與jedis的整合,請配置好spring與jedis架構,並配置好RedisTemplate這個bean。

首先編寫如下兩個類,將這兩個類配置到shiro的設定檔中。
由於是直接從公司的項目中拷貝源碼,所以對包名做了替換,讀者應根據實際情況調整刪減。

//認證與授權類package com.xxx.common.shiro;import java.util.List;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.cache.Cache;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.xxx.common.exception.ReadMessageException;import com.xxx.api.AuthorityApi;import com.xxx.api.RoleApi;import com.xxx.api.UserApi;import com.xxx.domain.Authority;import com.xxx.domain.Role;import com.xxx.domain.User;@Componentpublic class BasicAuthorizingRealm extends AuthorizingRealm {    private static final Logger logger=LogManager.getLogger(BasicAuthorizingRealm.class);    @Autowired private UserApi userApi;    @Autowired private RoleApi roleApi;    @Autowired private AuthorityApi authorityApi;    private static final String AUTHORIZATION_CACHE_NAME="authorization";    public BasicAuthorizingRealm() {        super.setAuthorizationCacheName(AUTHORIZATION_CACHE_NAME);    }    /***     * 擷取認證資訊     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {        UsernamePasswordToken token = (UsernamePasswordToken) at;        try {            User user = userApi.getByUsername(token.getUsername());            if(user==null) {                throw new AuthenticationException("使用者名稱或密碼錯誤");            }            String pwd=new String(token.getPassword());            if(!userApi.checkPassword(user.getId(), pwd)) {                throw new AuthenticationException("使用者名稱或密碼錯誤");            }            if(user.getStatus()==User.STATUS_DISABLED) {                throw new AuthenticationException("使用者已被禁用");            }            clearAuthorizationInfoCache(user);//使用者登入後,清除使用者緩衝,以便重新載入使用者權限            return new SimpleAuthenticationInfo(user, token.getPassword(), getName());        } catch (ReadMessageException e) {            logger.error(e);            throw new AuthenticationException(e);        } catch (AuthenticationException e) {            logger.error(e);            throw e;        }    }    /***     * 擷取授權資訊     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {        User user =(User)pc.fromRealm(getName()).iterator().next();        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();        try {            List<Role> roles=roleApi.queryRoles(user.getId());            for(Role role:roles) {                info.addRole(role.getCode());                List<Authority> auths=authorityApi.queryAuths(role.getId());                for(Authority auth:auths) {                    if(auth.getPermission()==null) break;                    String[] perms=auth.getPermission().split(","); //支援以逗號隔開的許可權標識                    for(String perm:perms) {                        info.addStringPermission(perm);                    }                }            }        } catch (ReadMessageException e) {            logger.error(e);        }        return info;    }    /**     * 清除所有使用者的緩衝     */    public void clearAuthorizationInfoCache() {        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();        if(cache!=null) {            cache.clear();        }    }    /**     * 清除指定使用者的緩衝     * @param user     */    private void clearAuthorizationInfoCache(User user) {        Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();        cache.remove(user.getId());    }}
//redis緩衝實作類別package com.xxx.common.shiro;import java.util.Collection;import java.util.Set;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheException;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.subject.PrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.BoundHashOperations;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import com.xxx.domain.User;@Componentpublic class RedisCacheManager implements CacheManager {    private String cacheKeyPrefix = "shiro:";    @Autowired private RedisTemplate<String, Object> redisTemplate;    @Override    public <K, V> Cache<K, V> getCache(String name) throws CacheException {        return new ShiroRedisCache<K,V>(cacheKeyPrefix+name);    }    /**     * 為shiro量身定做的一個redis cache,為Authorization cache做了特別最佳化     */    public class ShiroRedisCache<K, V> implements Cache<K, V> {        private String cacheKey;        public ShiroRedisCache(String cacheKey) {            this.cacheKey=cacheKey;        }        @Override        public V get(K key) throws CacheException {            BoundHashOperations<String,K,V> hash = redisTemplate.boundHashOps(cacheKey);            Object k=hashKey(key);            return hash.get(k);        }        @Override        public V put(K key, V value) throws CacheException {            BoundHashOperations<String,K,V> hash = redisTemplate.boundHashOps(cacheKey);            Object k=hashKey(key);            hash.put((K)k, value);            return value;        }        @Override        public V remove(K key) throws CacheException {            BoundHashOperations<String,K,V> hash = redisTemplate.boundHashOps(cacheKey);            Object k=hashKey(key);            V value=hash.get(k);            hash.delete(k);            return value;        }        @Override        public void clear() throws CacheException {            redisTemplate.delete(cacheKey);        }        @Override        public int size() {            BoundHashOperations<String,K,V> hash = redisTemplate.boundHashOps(cacheKey);            return hash.size().intValue();        }        @Override        public Set<K> keys() {            BoundHashOperations<String,K,V> hash = redisTemplate.boundHashOps(cacheKey);            return hash.keys();        }        @Override        public Collection<V> values() {            BoundHashOperations<String,K,V> hash = redisTemplate.boundHashOps(cacheKey);            return hash.values();        }        protected Object hashKey(K key) {            if(key instanceof PrincipalCollection) {//此處很重要,如果key是登入憑證,那麼這是訪問使用者的授權緩衝;將登入憑證轉為user對象,返回user的id屬性做為hash key,否則會以user對象做為hash key,這樣就不好清除指定使用者的緩衝了                PrincipalCollection pc=(PrincipalCollection) key;                User user =(User)pc.getPrimaryPrincipal();                return user.getId();            }            return key;        }    }}

將以上兩個類對象配置到shiro設定檔中

    <!-- 定義Shiro安全管理配置 -->    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">        <property name="realm" ref="basicAuthorizingRealm" />        <property name="cacheManager" ref="redisCacheManager" />    </bean>

至此,整合完畢,以下是我的登入介面與登出介面的Controller代碼,以供參考。

package com.xxx.controller.sys;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.subject.Subject;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RestController;import com.alibaba.fastjson.JSONObject;import com.xxx.common.JsonMessage;@RestController@RequestMapping("/api/sys")public class LoginController extends BaseController {    /**     * 登入使用者     * @param json     * @return     */    @RequestMapping(value = "/login", method = RequestMethod.POST)    public JsonMessage login(@RequestBody JSONObject json) {        String username = json.getString("username");        String password = json.getString("password");        Subject subject = SecurityUtils.getSubject();        if (subject.isAuthenticated()) {            return respOk();        }        UsernamePasswordToken token = new UsernamePasswordToken(username, password);        // token.setRememberMe(true);        subject.login(token);        return respOk();    }    /**     * 登出使用者     * @return     */    @RequestMapping(value = "/logout",method={RequestMethod.POST})    public JsonMessage logout() {        Subject subject = SecurityUtils.getSubject();        if (subject.isAuthenticated()) {            subject.logout();        }        return respOk();    }}

代碼比較多,需要有shiro,redis,spring mvc基礎才能理解,見諒。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.