shiro源碼篇 - shiro的session共用,你值得擁有

来源:https://www.cnblogs.com/youzhibing/archive/2018/10/30/9749427.html
-Advertisement-
Play Games

前言 開心一刻 老師對小明說:"乳就是小的意思,比如乳豬就是小豬,乳名就是小名,請你用乳字造個句" 小明:"我家很窮,只能住在40平米的乳房" 老師:"..., 這個不行,換一個" 小明:"我每天上學都要跳過我家門口的一條乳溝" 老師:"......, 這個也不行,再換一個" 小明:"老師,我想不出 ...


前言

  開心一刻

    老師對小明說:"乳就是小的意思,比如乳豬就是小豬,乳名就是小名,請你用乳字造個句"
    小明:"我家很窮,只能住在40平米的乳房"
    老師:"..., 這個不行,換一個"
    小明:"我每天上學都要跳過我家門口的一條乳溝"
    老師:"......, 這個也不行,再換一個"
    小明:"老師,我想不出來了,把我的乳頭都想破了!"

  路漫漫其修遠兮,吾將上下而求索!

  github:https://github.com/youzhibing

  碼雲(gitee):https://gitee.com/youzhibing

前情回顧

  shiro的session創建session的查詢、更新、過期、刪除中,shiro對session的操作基本都講到了,但還缺一個session共用沒有講解;session共用的原理其實在定義session管理一文已經講過了,本文不講原理,只看看shiro的session共用的實現。

  為何需要session共用

    如果是單機應用,那麼談不上session共用,session放哪都無所謂,不在乎放到預設的servlet容器中,還是抽出來放到單獨的地方;

    也就是說session共用是針對集群(或分散式、或分散式集群)的;如果不做session共用,仍然採用預設的方式(session存放到預設的servlet容器),當我們的應用是以集群的方式發佈的時候,同個用戶的請求會被分發到不同的集群節點(分發依賴具體的負載均衡規則),那麼每個處理同個用戶請求的節點都會重新生成該用戶的session,這些session之間是毫無關聯的。那麼同個用戶的請求會被當成多個不同用戶的請求,這肯定是不行的。

  如何實現session共用

    實現方式其實有很多,甚至可以不做session共用,具體有哪些,大家自行去查資料。本文提供一種方式:redis實現session共用,就是將session從servlet容器抽出來,放到redis中存儲,所有集群節點都從redis中對session進行操作。

SessionDAO

  SessionDAO其實是用於session持久化的,但裡面有緩存部分,具體細節我們往下看

  shiro已有SessionDAO的實現如下

  SessionDAO介面提供的方法如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;

import java.io.Serializable;
import java.util.Collection;


/**
 * 從EIS操作session的規範(EIS:例如關係型資料庫, 文件系統, 持久化緩存等等, 具體依賴DAO實現)
 * 提供了典型的CRUD的方法:create, readSession, update, delete
 */
public interface SessionDAO {

    /**
     * 插入一個新的sesion記錄到EIS 
     */
    Serializable create(Session session);

    /**
     * 根據會話ID獲取會話
     */
    Session readSession(Serializable sessionId) throws UnknownSessionException;

    /**
     * 更新session; 如更新session最後訪問時間/停止會話/設置超時時間/設置移除屬性等會調用
     */
    void update(Session session) throws UnknownSessionException;

    /**
     * 刪除session; 當會話過期/會話停止(如用戶退出時)會調用
     */
    void delete(Session session);

    /**
     * 獲取當前所有活躍session, 所有狀態不是stopped/expired的session
     * 如果用戶量多此方法影響性能
     */
    Collection<Session> getActiveSessions();
}
View Code

    SessionDAO給出了從持久層(一般而言是關係型資料庫)操作session的標準。

  AbstractSessionDAO提供了SessionDAO的基本實現,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SimpleSession;

import java.io.Serializable;


/**
 * SessionDAO的抽象實現, 在會話創建和讀取時做一些健全性檢查,併在需要時允許可插入的會話ID生成策略.
 * SessionDAO的update和delete則留給子類來實現
 * EIS需要子類自己實現
 */
public abstract class AbstractSessionDAO implements SessionDAO {

    /**
     * sessionId生成器
     */
    private SessionIdGenerator sessionIdGenerator;

    public AbstractSessionDAO() {
        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();    // 指定JavaUuidSessionIdGenerator為預設sessionId生成器
    }

    /**
     * 獲取sessionId生成器
     */
    public SessionIdGenerator getSessionIdGenerator() {
        return sessionIdGenerator;
    }

    /**
     * 設置sessionId生成器
     */
    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
        this.sessionIdGenerator = sessionIdGenerator;
    }

    /**
     * 生成一個新的sessionId, 並將它應用到session實例
     */
    protected Serializable generateSessionId(Session session) {
        if (this.sessionIdGenerator == null) {
            String msg = "sessionIdGenerator attribute has not been configured.";
            throw new IllegalStateException(msg);
        }
        return this.sessionIdGenerator.generateId(session);
    }

    /**
     * SessionDAO中create實現; 將創建的sesion保存到EIS.
     * 子類doCreate方法的代理,具體的細節委托給了子類的doCreate方法
     */
    public Serializable create(Session session) {
        Serializable sessionId = doCreate(session);
        verifySessionId(sessionId);
        return sessionId;
    }

    /**
     * 保證從doCreate返回的sessionId不是null,並且不是已經存在的.
     * 目前只實現了null校驗,是否已存在是沒有校驗的,可能shiro的開發者會在後續補上吧.
     */
    private void verifySessionId(Serializable sessionId) {
        if (sessionId == null) {
            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
            throw new IllegalStateException(msg);
        }
    }

    /**
     * 分配sessionId給session實例
     */
    protected void assignSessionId(Session session, Serializable sessionId) {
        ((SimpleSession) session).setId(sessionId);
    }

    /**
     * 子類通過實現此方法來持久化Session實例到EIS.
     */
    protected abstract Serializable doCreate(Session session);

    /**
     * SessionDAO中readSession實現; 通過sessionId從EIS獲取session對象.
     * 子類doReadSession方法的代理,具體的獲取細節委托給了子類的doReadSession方法.
     */
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session s = doReadSession(sessionId);
        if (s == null) {
            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
        }
        return s;
    }

    /**
     * 子類通過實現此方法從EIS獲取session實例
     */
    protected abstract Session doReadSession(Serializable sessionId);

}
View Code

    SessionDao的基本實現,實現了SessionDao的create、readSession(具體還是依賴AbstractSessionDAO子類的doCreate、doReadSession實現);同時加入了自己的sessionId生成器,負責sessionId的操作。

  CachingSessionDAO提供了session緩存的功能,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.CacheManagerAware;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;

/**
 * 應用層與持久層(EIS,如關係型資料庫、文件系統、NOSQL)之間的緩存層實現
 * 緩存著所有激活狀態的session
 * 實現了CacheManagerAware,會在shiro載入的過程中調用此對象的setCacheManager方法
 */
public abstract class CachingSessionDAO extends AbstractSessionDAO implements CacheManagerAware {

    /**
     * 激活狀態的sesion的預設緩存名
     */
    public static final String ACTIVE_SESSION_CACHE_NAME = "shiro-activeSessionCache";

    /**
     * 緩存管理器,用來獲取session緩存
     */
    private CacheManager cacheManager;

    /**
     * 用來緩存session的緩存實例
     */
    private Cache<Serializable, Session> activeSessions;

    /**
     * session緩存名, 預設是ACTIVE_SESSION_CACHE_NAME.
     */
    private String activeSessionsCacheName = ACTIVE_SESSION_CACHE_NAME;

    public CachingSessionDAO() {
    }

    /**
     * 設置緩存管理器
     */
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    /**
     * 獲取緩存管理器
     */
    public CacheManager getCacheManager() {
        return cacheManager;
    }

    /**
     * 獲取緩存實例的名稱,也就是獲取activeSessionsCacheName的值
     */
    public String getActiveSessionsCacheName() {
        return activeSessionsCacheName;
    }

    /**
     * 設置緩存實例的名稱,也就是設置activeSessionsCacheName的值
     */
    public void setActiveSessionsCacheName(String activeSessionsCacheName) {
        this.activeSessionsCacheName = activeSessionsCacheName;
    }

    /**
     * 獲取緩存實例
     */
    public Cache<Serializable, Session> getActiveSessionsCache() {
        return this.activeSessions;
    }

    /**
     * 設置緩存實例
     */
    public void setActiveSessionsCache(Cache<Serializable, Session> cache) {
        this.activeSessions = cache;
    }

    /**
     * 獲取緩存實例
     * 註意:不會返回non-null值
     *
     * @return the active sessions cache instance.
     */
    private Cache<Serializable, Session> getActiveSessionsCacheLazy() {
        if (this.activeSessions == null) {
            this.activeSessions = createActiveSessionsCache();
        }
        return activeSessions;
    }

    /**
     * 創建緩存實例
     */
    protected Cache<Serializable, Session> createActiveSessionsCache() {
        Cache<Serializable, Session> cache = null;
        CacheManager mgr = getCacheManager();
        if (mgr != null) {
            String name = getActiveSessionsCacheName();
            cache = mgr.getCache(name);
        }
        return cache;
    }

    /**
     * AbstractSessionDAO中create的重寫
     * 調用父類(AbstractSessionDAO)的create方法, 然後將session緩存起來
     * 返回sessionId
     */
    public Serializable create(Session session) {
        Serializable sessionId = super.create(session);    // 調用父類的create方法
        cache(session, sessionId);                        // 以sessionId作為key緩存session
        return sessionId;
    }

    /**
     * 從緩存中獲取session; 若sessionId為null,則返回null
     */
    protected Session getCachedSession(Serializable sessionId) {
        Session cached = null;
        if (sessionId != null) {
            Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
            if (cache != null) {
                cached = getCachedSession(sessionId, cache);
            }
        }
        return cached;
    }

    /**
     * 從緩存中獲取session
     */
    protected Session getCachedSession(Serializable sessionId, Cache<Serializable, Session> cache) {
        return cache.get(sessionId);
    }

    /**
     * 緩存session,以sessionId作為key
     */
    protected void cache(Session session, Serializable sessionId) {
        if (session == null || sessionId == null) {
            return;
        }
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache == null) {
            return;
        }
        cache(session, sessionId, cache);
    }

    protected void cache(Session session, Serializable sessionId, Cache<Serializable, Session> cache) {
        cache.put(sessionId, session);
    }

    /**
     * AbstractSessionDAO中readSession的重寫
     * 先從緩存中獲取,若沒有則調用父類的readSession方法獲取session
     */
    public Session readSession(Serializable sessionId) throws UnknownSessionException {
        Session s = getCachedSession(sessionId);        // 從緩存中獲取
        if (s == null) {
            s = super.readSession(sessionId);           // 調用父類readSession方法獲取
        }
        return s;
    }

    /**
     * SessionDAO中update的實現
     * 更新session的狀態
     */
    public void update(Session session) throws UnknownSessionException {
        doUpdate(session);                               // 更新EIS中的session
        if (session instanceof ValidatingSession) {
            if (((ValidatingSession) session).isValid()) {
                cache(session, session.getId());         // 更新緩存中的session
            } else {
                uncache(session);                        // 移除緩存中的sesson
            }
        } else {
            cache(session, session.getId());
        }
    }

    /**
     * 由子類去實現,持久化session到EIS
     */
    protected abstract void doUpdate(Session session);

    /**
     * SessionDAO中delete的實現
     * 刪除session
     */
    public void delete(Session session) {
        uncache(session);                                // 從緩存中移除
        doDelete(session);                               // 從EIS中刪除
    }

    /**
     * 由子類去實現,從EIS中刪除session
     */
    protected abstract void doDelete(Session session);

    /**
     * 從緩存中移除指定的session
     */
    protected void uncache(Session session) {
        if (session == null) {
            return;
        }
        Serializable id = session.getId();
        if (id == null) {
            return;
        }
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache != null) {
            cache.remove(id);
        }
    }

    /**
     * SessionDAO中getActiveSessions的實現
     * 獲取所有的存活的session
     */
    public Collection<Session> getActiveSessions() {
        Cache<Serializable, Session> cache = getActiveSessionsCacheLazy();
        if (cache != null) {
            return cache.values();
        } else {
            return Collections.emptySet();
        }
    }
}
View Code

    是應用層與持久化層之間的緩存層,不用頻繁請求持久化層以提升效率。重寫了AbstractSessionDAO中的create、readSession方法,實現了SessionDAO中的update、delete、getActiveSessions方法,預留doUpdate和doDelele給子類去實現(doXXX方法操作的是持久層)

  MemorySessionDAO,SessionDAO的簡單記憶體實現,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;


/**
 * 基於記憶體的SessionDao的簡單實現,所有的session存在ConcurrentMap中
 * DefaultSessionManager預設用的MemorySessionDAO
 */
public class MemorySessionDAO extends AbstractSessionDAO {

    private static final Logger log = LoggerFactory.getLogger(MemorySessionDAO.class);

    private ConcurrentMap<Serializable, Session> sessions;                // 存放session的容器

    public MemorySessionDAO() {
        this.sessions = new ConcurrentHashMap<Serializable, Session>();
    }

    // AbstractSessionDAO 中doCreate的重寫; 將session存入sessions
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);        // 生成sessionId
        assignSessionId(session, sessionId);                        // 將sessionId賦值到session
        storeSession(sessionId, session);                            // 存儲session到sessions
        return sessionId;
    }

    // 存儲session到sessions
    protected Session storeSession(Serializable id, Session session) {
        if (id == null) {
            throw new NullPointerException("id argument cannot be null.");
        }
        return sessions.putIfAbsent(id, session);
    }

    // AbstractSessionDAO 中doReadSession的重寫; 從sessions中獲取session
    protected Session doReadSession(Serializable sessionId) {
        return sessions.get(sessionId);
    }

    // SessionDAO中update的實現; 更新sessions中指定的session
    public void update(Session session) throws UnknownSessionException {
        storeSession(session.getId(), session);
    }

    // SessionDAO中delete的實現; 從sessions中移除指定的session
    public void delete(Session session) {
        if (session == null) {
            throw new NullPointerException("session argument cannot be null.");
        }
        Serializable id = session.getId();
        if (id != null) {
            sessions.remove(id);
        }
    }

    // SessionDAO中SessionDAO中delete的實現的實現; 獲取sessions中全部session
    public Collection<Session> SessionDAO中delete的實現() {
        Collection<Session> values = sessions.values();
        if (CollectionUtils.isEmpty(values)) {
            return Collections.emptySet();
        } else {
            return Collections.unmodifiableCollection(values);
        }
    }

}
View Code

    將session保存在記憶體中,存儲結構是ConcurrentHashMap;項目中基本不用,即使我們不實現自己的SessionDAO,一般用的也是EnterpriseCacheSessionDAO。

  EnterpriseCacheSessionDAO,提供了緩存功能的session維護,如下

package org.apache.shiro.session.mgt.eis;

import org.apache.shiro.cache.AbstractCacheManager;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.MapCache;
import org.apache.shiro.session.Session;

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

public class EnterpriseCacheSessionDAO extends CachingSessionDAO {

    public EnterpriseCacheSessionDAO() {
        
        // 設置預設緩存器,並實例化MapCache作為cache實例
        setCacheManager(new AbstractCacheManager() {
            @Override
            protected Cache<Serializable, Session> createCache(String name) throws CacheException {
                return new MapCache<Serializable, Session>(name, new ConcurrentHashMap<Serializable, Session>());
            }
        });
    }

    // AbstractSessionDAO中doCreate的重寫; 
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        return sessionId;
    }

    // AbstractSessionDAO中doReadSession的重寫
    protected Session doReadSession(Serializable sessionId) {
        return null; //should never execute because this implementation relies on parent class to access cache, which
        //is where all sessions reside - it is the cache implementation that determines if the
        //cache is memory only or disk-persistent, etc.
    }

    // CachingSessionDAO中doUpdate的重寫
    protected void doUpdate(Session session) {
        //does nothing - parent class persists to cache.
    }

    // CachingSessionDAO中doDelete的重寫
    protected void doDelete(Session session) {
        //does nothing - parent class removes from cache.
    }
}
View Code

    設置了預設的緩存管理器(AbstractCacheManager)和預設的緩存實例(MapCache),實現了緩存效果。從父類繼承的持久化操作方法(doXXX)都是空實現,也就說EnterpriseCacheSessionDAO是沒有實現持久化操作的,僅僅只是簡單的提供了緩存實現。當然我們可以繼承EnterpriseCacheSessionDAO,重寫doXXX方法來實現持久化操作。

  總結下:SessionDAO定義了從持久層操作session的標準;AbstractSessionDAO提供了SessionDAO的基礎實現,如生成會話ID等;CachingSessionDAO提供了對開發者透明的session緩存的功能,只需要設置相應的 CacheManager 即可;MemorySessionDAO直接在記憶體中進行session維護;而EnterpriseCacheSessionDAO提供了緩存功能的session維護,預設情況下使用 MapCache 實現,內部使用ConcurrentHashMap保存緩存的會話。因為shiro不知道我們需要將session持久化到哪裡(關係型資料庫,還是文件系統),所以只提供了MemorySessionDAO持久化到記憶體(聽起來怪怪的,記憶體中能說成持久層嗎)

shiro session共用

  共用實現

    shiro的session共用其實是比較簡單的,重寫CacheManager,將其操作指向我們的redis,然後實現我們自己的CachingSessionDAO定製緩存操作和緩存持久化。

    自定義CacheManager

      ShiroRedisCacheManager

package com.lee.shiro.config;

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;
import org.springframework.stereotype.Component;

@Component
public class ShiroRedisCacheManager implements CacheManager {

    @Autowired
    private Cache shiroRedisCache;

    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        return shiroRedisCache;
    }
}
View Code

      ShiroRedisCache

package com.lee.shiro.config;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

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

@Component
public class ShiroRedisCache<K,V> implements Cache<K,V>{

    @Autowired
    private RedisTemplate<K,V> redisTemplate;

    @Value("${spring.redis.expireTime}")
    private long expireTime;

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

    @Override
    public V put(K k, V v) throws CacheException {
        redisTemplate.opsForValue().set(k,v,expireTime, TimeUnit.SECONDS);
        return null;
    }

    @Override
    public V remove(K k) throws CacheException {
        V v = redisTemplate.opsForValue().get(k);
        redisTemplate.opsForValue().getOperations().delete(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;
    }
}
View Code

    自定義CachingSessionDAO

      繼承EnterpriseCacheSessionDAO,然後重新設置其CacheManager(替換掉預設的記憶體緩存器),這樣也可以實現我們的自定義CachingSessionDAO,但是這是優選嗎;如若我們實現持久化,繼承EnterpriseCacheSessionDAO是優選,但如果只是實現session緩存,那麼CachingSessionDAO是優選,自定義更靈活。那麼我們還是繼承CachingSessionDAO來實現我們的自定義CachingSessionDAO

      ShiroSessionDAO

package com.lee.shiro.config;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component
public class ShiroSessionDAO extends CachingSessionDAO {

    @Override
    protected void doUpdate(Session session) {
    }

    @Override
    protected void doDelete(Session session) {
    }

    @Override
    protected Serializable doCreate(Session session) {
        // 這裡綁定sessionId到session,必須要有
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        return null;
    }
}
View Code

    最後將ShiroSessionDAO實例賦值給SessionManager實例,再講SessionManager實例賦值給SecurityManager實例即可

    具體代碼請參考spring-boot-shiro

  源碼解析

    底層還是利用Filter + HttpServletRequestWrapper將對session的操作接入到自己的實現中來,而不走預設的servlet容器,這樣對session的操作完全由我們自己掌握。

    shiro的session創建中其實講到了shiro中對session操作的基本流程,這裡不再贅述,沒看的朋友可以先去看看再回過頭來看這篇。本文只講shiro中,如何將一個請求的session接入到自己的實現中來的;shiro中有很多預設的filter,我會單獨開一篇來講shiro的filter,這篇我們先不糾結這些filter。

    OncePerRequestFilter中doFilter方法如下

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
    if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {    // 當前filter已經執行過了,進行下一個filter
        log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());
        filterChain.doFilter(request, response);
    } else //noinspection deprecation
        if (/* added in 1.2: */ !isEnabled(request, response) ||
            /* retain backwards compatibility: */ shouldNotFilter(request) ) {    // 當前filter未被啟用或忽略此filter,則進行下一個filter;shouldNotFilter已經被廢棄了
        log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",
                getName());
        filterChain.doFilter(request, response);
    } else {
        // Do invoke this filter...
        log.trace("Filter '{}' not yet executed.  Executing now.", getName());
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

        try {
            // 執行當前filter
            doFilterInternal(request, response, filterChain);
        } finally {
            // 一旦請求完成,我們清除當前filter的"已經過濾"的狀態
            request.removeAttribute(alreadyFilteredAttributeName);
        }
    }
}
View Code

    上圖中,我可以看到AbstractShiroFilter的doFilterInternal放中將request封裝成了shiro自定義的ShiroHttpServletRequest,將response也封裝成了shiro自定義的ShiroHttpServletResponse。既然Filter中將request封裝了ShiroHttpServletRequest,那麼到我們應用的request就是ShiroHttpServletRequest類型,也就是說我們對session的操作最終都是由shiro完成的,而不是預設的servlet容器。

    另外補充一點,shiro的session創建不是懶創建的。servlet容器中的session創建是第一次請求session(第一調用request.getSession())時才創建。shiro的session創建如下圖

    此時,還沒登錄,但是subject、session已經創建了,只是subject的認證狀態為false,說明還沒進行登錄認證的。至於session創建過程已經保存到redis的流程需要大家自行去跟,或者閱讀我之前的博文

<
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一,效果圖。 二,代碼。 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>html 列表</title> </head> <body> <!--創建文本欄位text field--> <form action=""> first ...
  • 我們在做css的時候為了提高網站的效率減少伺服器請求,我們可以通過css來實現一些簡單的圖片特效,比如說三角形,這篇文章講解的是通過邊框實現不同的效果。 首先看一下不同邊框樣式的效果: 代碼部分: 1 <style type="text/css"> 2 .style-border b { 3 bor ...
  • jQuery CDN PS:jQuery 2.0 以上版本不再支持IE 6/7/8 REF: "https://www.sojson.com/jquery/down.html" 經多次測試,建議使用百度的CDN引用地址,官網的地址有可能造成部分地區打開略有延遲 Version Baidu Examp ...
  • Vue 組件之間傳值 一、父組件向子組件傳遞數據 在 Vue 中,可以使用 props 向子組件傳遞數據。 子組件部分: 這是 header.vue 的 HTML 部分,logo 是在 data 中定義的變數。 如果需要從父組件獲取 logo 的值,就需要使用 props: ['logo'] 在 p ...
  • ```php ...
  • 備忘錄,備份曾經發生過的歷史記錄,以防忘記,之後便可以輕鬆回溯過往。想必我們曾經都乾過很多蠢事導致糟糕的結果,當後悔莫及的時候已經是覆水難收了,只可惜這世界上沒有後悔藥,事後我們能做的只能去彌補過失,總結經驗。除非穿越時空,時光倒流,利用愛因斯坦狹義相對論,超越光速回到過去,破鏡重圓。 然而世界是殘 ...
  • 先編一個這麼久不寫的理由 上周我終於鼓起勇氣翻開了headfirst設計模式這本書,看看自己下一個設計模式要寫個啥,然後,我終於知道我為啥這麼久都沒寫設計模式了,headfirst的這個抽象工廠模式,額,我看了好幾次,都不太理解。 在我的印象中,簡單工廠,工廠方法,抽象工廠,這三個東西應該是層層遞進 ...
  • sentence="知之為知之不知為不知"dict1={}for s in sentence: dict1[s]=dict1.setdefault(s,0)+1print(dict1) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...