天天看點

Apache Shiro安全架構(5)-會話管理和緩存

會話

會話,即使用者保持和服務端之間的聯系,保證使用者在下一次通路服務端時不必在送出使用者身份資訊。而服務端可以通過使用者送出的sessionId判斷使用者身份。

Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession()
           

可以通過以上方法擷取目前登入用

/**
 *Subject.getSession(true),即如果目前沒有建立session對象會建立一個;
 *Subject.getSession(false),如果目前沒有建立session對象則傳回null。
*/
Subject.getSession();//等價于Subject.getSession(true)

session.getId();//擷取目前會話的唯一辨別。

session.getHost();//擷取目前會話的主機位址。

session.getTimeout() & session.setTimeout(毫秒);//設定/擷取目前Session的過期時間。

/**擷取會話的啟動時間及最後通路時間;
 *如果是J2SE環境需要自己定期調用session.touch()去更新最後通路時間;
 *如果是Web環境,每次進入ShiroFilter都會自動調用session.touch()來更新最後通路時間。
*/
session.getStartTimestamp() & session.getLastAccessTime();

/**
 *更新會話最後通路時間以及銷毀會話;
 *Subject.logout()會自動調用session.stop()。
 *在Web應用中,調用HttpSession.invalidate()也會自動調用session.stop()來銷毀shiro的會話。
*/
session.touch() & session.stop();

session.setAttribute(key,val) & session.getAttribute(key) & session.removeAttribute(key);//設定/擷取/删除 會話屬性。
           

會話管理

shiro中的會話管理器管理所有Subject的會話的建立、删除、驗證、失效等。shiro中會話管理的結構圖如下:

Apache Shiro安全架構(5)-會話管理和緩存

發現SecurityManager繼承了SessionManager接口,而SecurityManager結構圖如下:

Apache Shiro安全架構(5)-會話管理和緩存

發現SessionSecurityManager實作了接口SecurityManager,實質SessionSecurityManager實作了把會話管理委托給對應的SessionManager。而DefaultSecurityManager和DefaultWebSecurityManager都繼承了SessionSecurityManager

會話監聽器

自定義類實作SessionListener接口,并實作其中的方法。SessionListener方法如下:

  • onStart(Session):建立會話時觸發該事件
  • onStop(Session):銷毀會話時觸發該事件
  • onExpiration(Session):會話過期時觸發該事件

Session的CURD

shiro中SessionDao的結構圖

Apache Shiro安全架構(5)-會話管理和緩存
  • AbstractSessionDAO 提供了 SessionDAO 的基礎實作,如生成會話 ID 等;
  • CachingSessionDAO 提供了對開發者透明的會話緩存的功能,隻需要在SecurityManager設定相應的 CacheManager即可
  • MemorySessionDAO 直接在記憶體中進行會話維護;
  • EnterpriseCacheSessionDAO提供了緩存功能的會話維護,預設情況下使用 MapCache 實作,内部使用ConcurrentHashMap 儲存緩存的會話

以上是shiro中會話管理的基本介紹,接下來看看在shiro中如何自定義會話管理:

自定義sessionDao:

import com.heyu.framework.service.RedisService;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

public class RedisSessionDao extends EnterpriseCacheSessionDAO {

    @Autowired
    private RedisService<String,Object>  redisService;

    private static final String PREFIX = "shiro_session";

    private static Long expire = 3600L;

    @Override
    protected void doUpdate(Session session) {
        super.doUpdate(session);
        String key = PREFIX + session.getId().toString();
        if(!redisService.exists(key)){
            redisService.set(key,session);
        }
        redisService.expire(key,expire,TimeUnit.SECONDS);

    }

    @Override
    protected void doDelete(Session session) {
        super.doDelete(session);
        redisService.delete(PREFIX + session.getId());
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = super.doCreate(session);
        redisService.set(PREFIX + sessionId.toString(),session);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable serializable) {

        Session session = super.doReadSession(serializable);
        if(session == null){
            session = (Session) redisService.get(PREFIX + serializable.toString());
        }
        return session;
    }

}
           

通過該類中的方法名便知道,什麼時候觸發該方法的調用

配置會話管理:

@Bean
    public RedisSessionDao redisSessionDao(){
        RedisSessionDao redisSessionDao = new RedisSessionDao();
        return redisSessionDao;
    }

    @Bean
    public SessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDao());
        return sessionManager;
    }
           

再将執行個體化SecurityManager中的SessionManager屬性即可:

@Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //内部方法調用realm的getAuthenticationInfo擷取認證資訊
        securityManager.setRealm(authRealm());
        securityManager.setCacheManager(redisCacheManager());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }
           

緩存管理

shiro中提供Cache的接口:

public interface Cache<K, V> {
    //根據Key擷取緩存中的值
    public V get(K key) throws CacheException;
    //往緩存中放入key-value,傳回緩存中之前的值
    public V put(K key, V value) throws CacheException; 
    //移除緩存中key對應的值,傳回該值
    public V remove(K key) throws CacheException;
    //清空整個緩存
    public void clear() throws CacheException;
    //傳回緩存大小
    public int size();
    //擷取緩存中所有的key
    public Set<K> keys();
    //擷取緩存中所有的value
    public Collection<V> values();
}
           

提供的 CacheManager接口:

public interface CacheManager {
    //根據緩存名字擷取一個Cache
    public <K, V> Cache<K, V> getCache(String name) throws CacheException;
}
           

在shiro的内部元件SecurityManager中提供了CachingSecurityManager接口,内部元件Realm也有子接口CachingRealm。

shiro内部的緩存管理結構:

Apache Shiro安全架構(5)-會話管理和緩存

但是更多的是實作CacheManager接口,實作其中的getCache()方法,然後在建立Cache的實作類,getCche()方法傳回該實作類的執行個體

以下示範利用redis自定義緩存管理:

Cache實作類:

import com.heyu.framework.service.RedisService;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collection;
import java.util.Set;

public class RedisCache<K,V> implements Cache<K,V> {

    @Autowired
    private RedisService redisService;

    private String cacheKey;

    private static Long expire = 3600L;

    private static final String PREFIX = "SHIRO_CACHE";

    public RedisCache(String name,RedisService redisService){
        this.redisService = redisService;
        this.cacheKey = PREFIX + name;
    }

    public K getCacheKey(Object k) {
        return (K)(this.cacheKey+k);
    }

    public void setCacheKey(String cacheKey) {
        this.cacheKey = cacheKey;
    }

    @Override
    public V get(K k) throws CacheException {
        return (V) redisService.get(getCacheKey(k));
    }

    @Override
    public V put(K k, V v) throws CacheException {
        redisService.set(getCacheKey(k),v,expire);
        return v;
    }

    @Override
    public V remove(K k) throws CacheException {
        V v = (V) redisService.get(getCacheKey(k));
        redisService.delete(getCacheKey(k));
        return v;
    }

    @Override
    public void clear() throws CacheException {

    }

    @Override
    public int size() {
        return 0;
    }

    @Override
    public Set<K> keys() {
        return null;
    }

    @Override
    public Collection<V> values() {
        return null;
    }
}
           

redis操作類RedisSerivce:

import com.heyu.framework.exception.CommonException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService<K,V> {

    private RedisTemplate<K,V> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {
        RedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        this.redisTemplate = redisTemplate;
    }

    /**
     * 将資料存入緩存
     * @param key
     * @param value
     * @return
     */
    public boolean set(K key,V value){
        boolean result = false;
        try {
            ValueOperations<K,V> operations = redisTemplate.opsForValue();
            operations.set(key,value);
            result = true;

        }catch (Exception e){
            e.printStackTrace();
            throw new CommonException(e.getMessage());
        }
        return  result;
    }

    /**
     * 将資料存入緩存,并設定有效期,機關為秒
     * @param key
     * @param value
     * @param expire
     * @return
     */
    public boolean set(K key,V value,Long expire){
        boolean result = false;
        try {
            ValueOperations<K,V> operations = redisTemplate.opsForValue();
            operations.set(key,value,expire,TimeUnit.SECONDS);
            result = true;

        }catch (Exception e){
            e.printStackTrace();
            throw new CommonException(e.getMessage());
        }
        return  result;
    }

    /**
     * 從緩存中擷取資料
     * @param key
     * @return
     */
    public Object get(K key){
        Object result = null;
        ValueOperations<K,V> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        return result;
    }

    /**
     * 根據key從緩存中删除該key-value
     * @param key
     */
    public void delete(K key){
        if(exists(key)){
            redisTemplate.delete(key);
        }
    }

    /**
     * 批量删除
     * @param pattern
     */
    public void deletePattern(K pattern){
        Set<K> keys = redisTemplate.keys(pattern);
        if (keys != null && keys.size() > 0){
            redisTemplate.delete(keys);
        }
    }
    /**
     * 判斷緩存中 是否存在該key
     * @param key
     * @return
     */
    public boolean exists(K key){
        return redisTemplate.hasKey(key);
    }

    public void expire(K key,Long expireTime,TimeUnit unit){
        redisTemplate.expire(key,expireTime,unit);
    }
}
           

CacheManager實作類:

import com.heyu.framework.service.RedisService;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 自定義shiro中的緩存管理器,使用redis緩存
 */
public class RedisCacheManager implements CacheManager {

    @Autowired
    private RedisService<String,Object> redisService;

    @Override
    public <K, V> Cache<K, V> getCache(String name) throws CacheException {
        return new RedisCache<>(name,redisService);
    }

    public RedisService<String, Object> getRedisService() {
        return redisService;
    }

    public void setRedisService(RedisService<String, Object> redisService) {
        this.redisService = redisService;
    }
}
           

以上就完成了shiro中緩存的自定義,接下來隻需執行個體化SecurityManager中的CacheManager屬性:

@Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //内部方法調用realm的getAuthenticationInfo擷取認證資訊
        securityManager.setRealm(authRealm());
        securityManager.setCacheManager(redisCacheManager());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }
           
@Bean
    public RedisCacheManager redisCacheManager(){
        RedisCacheManager cacheManager = new RedisCacheManager();
        return cacheManager;
    }