1. 程式人生 > >2.詳解SecurityManager(shiro許可權管理門面)

2.詳解SecurityManager(shiro許可權管理門面)

SecurityManager 介面主要作用
為什麼要先說SecurityManager呢?因為我覺得他是shiro的主要入口,幾乎所有相關的許可權操作,都由他代理了。
1.可以說是所有配置的入口,簡化配置,方便使用。
2.一個介面就可以實現,驗證的操作(登入、退出)、授權(授權訪問指定資源、角色)、Session管理,相當於這些操作的門面(門面模式,也叫外觀模式)。

這裡寫圖片描述

從上圖我們可以看出SecurityManager各個子類的作用及子類的依賴元件的介面。下面我們底層子類往上推,分別詳解各個子類的作用
1. CachingSecurityManager

//該抽象類實現類CacheManagerAware 介面,主要提供快取支援,管理快取操作
public abstract class CachingSecurityManager implements SecurityManager, Destroyable, CacheManagerAware { //快取介面的實現類,實現可以是ehcache,java的HashMap版的cacheManager,redis private CacheManager cacheManager; //通過該介面可以獲取cacheManager,然後使用它來做一些你所需的操作,如進行一些自定義的快取管理 public CacheManager getCacheManager
() { return cacheManager; } public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; //在設定cacheManager後,執行一些後續操作。該後續操作主要是:設定cacheManagr到Realm和SessionManager等相關依cacheManagr的類 afterCacheManagerSet(); } //Template 方法,讓子類重寫實現業務邏輯
protected void afterCacheManagerSet() { } //銷燬SecurityManager時,銷燬該快取例項 public void destroy() { LifecycleUtils.destroy(getCacheManager()); this.cacheManager = null; } }

2.RealmSecurityManager

//該抽象類主要是管理Realm(可以理解為資料處理元件,比如Realm根據使用者名稱查詢底層資料庫,然後取出來和你輸入的使用者密碼進行批評,驗證是否equals來驗證是否登陸成功,還有授權資料等)
public abstract class RealmSecurityManager extends CachingSecurityManager {
    //至少需要一個Realm,可以是多個。相當於多個數據處理中心,比如mysql資料庫,redis資料庫
    private Collection<Realm> realms;

    public void setRealm(Realm realm) {
        if (realm == null) {
            throw new IllegalArgumentException("Realm argument cannot be null");
        }
        Collection<Realm> realms = new ArrayList<Realm>(1);
        realms.add(realm);
        setRealms(realms);
    }


    public void setRealms(Collection<Realm> realms) {
        if (realms == null) {
            throw new IllegalArgumentException("Realms collection argument cannot be null.");
        }
        if (realms.isEmpty()) {
            throw new IllegalArgumentException("Realms collection argument cannot be empty.");
        }
        this.realms = realms;
        //模板方法,定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中
        afterRealmsSet();
    }

    protected void afterRealmsSet() {
        applyCacheManagerToRealms();
    }

    //獲取realm,根據你是否有需求來決定使用。可能應用場景:從Realm中獲取使用者的授權、驗證的快取資訊
    public Collection<Realm> getRealms() {
        return realms;
    }

   //設定cacheManager到Realm中,因為Realm是資料處理元件,當它從資料庫中載入資料且得到正確的驗證後,可以快取到cacheManager,來提供效能。在shiro裡,AuthorizationInfo(授權資料)和AuthenticationInfo(使用者基本賬戶密碼)都被快取到cacheManager在驗證成功後
    protected void applyCacheManagerToRealms() {
        CacheManager cacheManager = getCacheManager();
        Collection<Realm> realms = getRealms();
        if (cacheManager != null && realms != null && !realms.isEmpty()) {
            for (Realm realm : realms) {
                if (realm instanceof CacheManagerAware) {
                    ((CacheManagerAware) realm).setCacheManager(cacheManager);
                }
            }
        }
    }
    protected void afterCacheManagerSet() {
        applyCacheManagerToRealms();
    }
    //銷燬realms
    public void destroy() {
        LifecycleUtils.destroy(getRealms());
        this.realms = null;
        super.destroy();
    }

}

3.AuthenticatingSecurityManager

//實現介面Authenticator,處理使用者登陸驗證的 SecurityManager 的 抽象實現,僅僅代理Authenticator.
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {

    //依賴authenticator驗證器,真正的登陸驗證都在這裡面處理
    private Authenticator authenticator;


    public AuthenticatingSecurityManager() {
        super();
    //預設的Authenticator,在大多數情況下,該預設例項足夠用了
        this.authenticator = new ModularRealmAuthenticator();
    }


    public Authenticator getAuthenticator() {
        return authenticator;
    }

    //覆蓋預設的ModularRealmAuthenticator
    public void setAuthenticator(Authenticator authenticator) throws IllegalArgumentException {
        if (authenticator == null) {
            String msg = "Authenticator argument cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        this.authenticator = authenticator;
    }

    //重寫父類模板方法
    protected void afterRealmsSet() {
        super.afterRealmsSet();
        if (this.authenticator instanceof ModularRealmAuthenticator) {
            ((ModularRealmAuthenticator) this.authenticator).setRealms(getRealms());
        }
    }

    //該類的核心,呼叫底層的authenticator進行使用者輸入的賬戶密碼驗證
    //token 是使用者輸入的賬戶密碼
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

    //銷燬Authenticator
    public void destroy() {
        LifecycleUtils.destroy(getAuthenticator());
        this.authenticator = null;
        super.destroy();
    }
}

4.AuthorizingSecurityManager

//AuthorizingSecurityManager是實現了Authorizer介面的抽象類,該類主要代理Authorizer進行授權。
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {

    //真正的授權器
    private Authorizer authorizer;


    public AuthorizingSecurityManager() {
        super();
    //預設的Authorizer,該預設例項支援大部分使用場景
        this.authorizer = new ModularRealmAuthorizer();
    }


    public Authorizer getAuthorizer() {
        return authorizer;
    }

    //覆蓋預設的Authorizer
    public void setAuthorizer(Authorizer authorizer) {
        if (authorizer == null) {
            String msg = "Authorizer argument cannot be null.";
            throw new IllegalArgumentException(msg);
        }
        this.authorizer = authorizer;
    }

    //重寫父類的afterRealmsSet(),首先呼叫父類的afterRealmsSet(),然後在設定realms到授權器,如果實現是ModularRealmAuthorizer。也就是說如果你覆蓋了預設的
    ModularRealmAuthorizer,那麼你要對這個方法進行處理。
    protected void afterRealmsSet() {
        super.afterRealmsSet();
        if (this.authorizer instanceof ModularRealmAuthorizer) {
            ((ModularRealmAuthorizer) this.authorizer).setRealms(getRealms());
        }
    }

    public void destroy() {
        LifecycleUtils.destroy(getAuthorizer());
        this.authorizer = null;
        super.destroy();
    }

    /**
    以下都是呼叫底層的authorizer來進行授權處理。所有的使用者是否能授權其實就是呼叫以下方法。後面詳講authorizer,會講解每個方法的意思,大家可以隨便看看有個影像就可以了。
    總的來說就兩類:一類是返回true false來說明是否授權成功,一個拋異常來說明是否授權成功
    **/
    public boolean isPermitted(PrincipalCollection principals, String permissionString) {
        return this.authorizer.isPermitted(principals, permissionString);
    }

    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
        return this.authorizer.isPermitted(principals, permission);
    }

    public boolean[] isPermitted(PrincipalCollection principals, String... permissions) {
        return this.authorizer.isPermitted(principals, permissions);
    }

    public boolean[] isPermitted(PrincipalCollection principals, List<Permission> permissions) {
        return this.authorizer.isPermitted(principals, permissions);
    }

    public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {
        return this.authorizer.isPermittedAll(principals, permissions);
    }

    public boolean isPermittedAll(PrincipalCollection principals, Collection<Permission> permissions) {
        return this.authorizer.isPermittedAll(principals, permissions);
    }

    public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
        this.authorizer.checkPermission(principals, permission);
    }

    public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
        this.authorizer.checkPermission(principals, permission);
    }

    public void checkPermissions(PrincipalCollection principals, String... permissions) throws AuthorizationException {
        this.authorizer.checkPermissions(principals, permissions);
    }

    public void checkPermissions(PrincipalCollection principals, Collection<Permission> permissions) throws AuthorizationException {
        this.authorizer.checkPermissions(principals, permissions);
    }

    public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
        return this.authorizer.hasRole(principals, roleIdentifier);
    }

    public boolean[] hasRoles(PrincipalCollection principals, List<String> roleIdentifiers) {
        return this.authorizer.hasRoles(principals, roleIdentifiers);
    }

    public boolean hasAllRoles(PrincipalCollection principals, Collection<String> roleIdentifiers) {
        return this.authorizer.hasAllRoles(principals, roleIdentifiers);
    }

    public void checkRole(PrincipalCollection principals, String role) throws AuthorizationException {
        this.authorizer.checkRole(principals, role);
    }

    public void checkRoles(PrincipalCollection principals, Collection<String> roles) throws AuthorizationException {
        this.authorizer.checkRoles(principals, roles);
    }

    public void checkRoles(PrincipalCollection principals, String... roles) throws AuthorizationException {
        this.authorizer.checkRoles(principals, roles);
    }    
}

5.SessionsSecurityManager

//SessionsSecurityManager實現類SessionManager的方法。代理了SessionManager來處理相關的session操作。
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {

    //Session管理
    private SessionManager sessionManager;


    public SessionsSecurityManager() {
        super();
    //預設的SessionManager的實現類
        this.sessionManager = new DefaultSessionManager();
    //顧名思義,設定CacheManager到sessionManager
        applyCacheManagerToSessionManager();
    }

     public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
        afterSessionManagerSet();
    }

    //在sessionManager被設定後呼叫,該類只調用 applyCacheManagerToSessionManager(),大家可以琢磨下這樣寫的好處,子類可以重寫增強等
    protected void afterSessionManagerSet() {
        applyCacheManagerToSessionManager();
    }

    //獲取sessionManager,根據你的需求。除了下面的方法,可能使用場景:獲取所有線上session
    public SessionManager getSessionManager() {
        return this.sessionManager;
    }

    //增強父類的afterCacheManagerSet(),滿足本類需求
    protected void afterCacheManagerSet() {
        super.afterCacheManagerSet();
        applyCacheManagerToSessionManager();
    }

    //如果sessionManager實現了CacheManagerAware,則設定cacheManager
    protected void applyCacheManagerToSessionManager() {
        if (this.sessionManager instanceof CacheManagerAware) {
            ((CacheManagerAware) this.sessionManager).setCacheManager(getCacheManager());
        }
    }

    //呼叫sessionManager根據上下文context(儲存session的相關建立資訊,大家可以去了解下上下文)建立個session
    public Session start(SessionContext context) throws AuthorizationException {
        return this.sessionManager.start(context);
    }

    //根據sessionID來獲取session,每次生成session後會生成個sessionID返回給瀏覽器,瀏覽器用sessionID來和sessionManager進行互動
    public Session getSession(SessionKey key) throws SessionException {
        return this.sessionManager.getSession(key);
    }

    //銷燬sessionManager
    public void destroy() {
        LifecycleUtils.destroy(getSessionManager());
        this.sessionManager = null;
        super.destroy();
    }
}

6.DefaultSecurityManager

//預設的SessionManager的具體實現,該實現類會合適的初始化依賴元件。如subjectFactory、SubjectDAO
public class DefaultSecurityManager extends SessionsSecurityManager {

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

    //是否記住密碼服務元件
    protected RememberMeManager rememberMeManager;
    //subject(代理特定的一個使用者的所有許可權相關操作,登陸退出、授權,獲取session等)的管理元件,僅提供了save和delete方法。用於儲存subject的狀態,方便與以後可以重建subject
    protected SubjectDAO subjectDAO;
    //根據SubjectContext(subject的狀態和相關的資料)來建立個subject
    protected SubjectFactory subjectFactory;


    public DefaultSecurityManager() {
        super();
        this.subjectFactory = new DefaultSubjectFactory();
        this.subjectDAO = new DefaultSubjectDAO();
    }


    public DefaultSecurityManager(Realm singleRealm) {
        this();
        setRealm(singleRealm);
    }

    //realms 呼叫子類的 setRealms(realms),至少需要一個。
    public DefaultSecurityManager(Collection<Realm> realms) {
        this();
        setRealms(realms);
    }

    //獲取subjectFactory,主要用於建立subject
    public SubjectFactory getSubjectFactory() {
        return subjectFactory;
    }


    public void setSubjectFactory(SubjectFactory subjectFactory) {
        this.subjectFactory = subjectFactory;
    }

    //獲取subjectDAO,主要用於持久化或者刪除subject狀態(該狀態可用於以後來重建subject)
    public SubjectDAO getSubjectDAO() {
        return subjectDAO;
    }


    public void setSubjectDAO(SubjectDAO subjectDAO) {
        this.subjectDAO = subjectDAO;
    }

    public RememberMeManager getRememberMeManager() {
        return rememberMeManager;
    }

    public void setRememberMeManager(RememberMeManager rememberMeManager) {
        this.rememberMeManager = rememberMeManager;
    }

    protected SubjectContext createSubjectContext() {
        return new DefaultSubjectContext();
    }

    //根據形參,建立個subject。
    //token使用者輸入的賬戶密碼,info從資料庫載入的賬戶密碼
    protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {
    //建立預設的SubjectContext的實現
        SubjectContext context = createSubjectContext();
    //設定context的是否驗證、token和info,方便以後呼叫
        context.setAuthenticated(true);
        context.setAuthenticationToken(token);
        context.setAuthenticationInfo(info);
        if (existing != null) {
            context.setSubject(existing);
        }
        return createSubject(context);
    }

    //shiri1.2之後已被棄用
    @Deprecated
    protected void bind(Subject subject) {
        save(subject);
    }

    //執行RememberMeManager的onSuccessfulLogin(subject, token, info)
    protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
        RememberMeManager rmm = getRememberMeManager();
        if (rmm != null) {
            try {
        //記住當前subject, principals
                rmm.onSuccessfulLogin(subject, token, info);
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
                            "] threw an exception during onSuccessfulLogin.  RememberMe services will not be " +
                            "performed for account [" + info + "].";
                    log.warn(msg, e);
                }
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("This " + getClass().getName() + " instance does not have a " +
                        "[" + RememberMeManager.class.getName() + "] instance configured.  RememberMe services " +
                        "will not be performed for account [" + info + "].");
            }
        }
    }

    protected void rememberMeFailedLogin(AuthenticationToken token, AuthenticationException ex, Subject subject) {
        RememberMeManager rmm = getRememberMeManager();
        if (rmm != null) {
            try {
            //清除cookie(重置cookie為預設值)
                rmm.onFailedLogin(subject, token, ex);
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
                            "] threw an exception during onFailedLogin for AuthenticationToken [" +
                            token + "].";
                    log.warn(msg, e);
                }
            }
        }
    }

    protected void rememberMeLogout(Subject subject) {
        RememberMeManager rmm = getRememberMeManager();
        if (rmm != null) {
            try {
        //清除cookie(重置cookie為預設值)
                rmm.onLogout(subject);
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
                            "] threw an exception during onLogout for subject with principals [" +
                            (subject != null ? subject.getPrincipals() : null) + "]";
                    log.warn(msg, e);
                }
            }
        }
    }

    /**
    使用者登陸驗證方法
    1.首先呼叫authenticate(token)驗證當前使用者登陸資訊;
    2.登陸驗證成功後,建立當前使用者的subject且繫結到當前執行緒和把subject的狀態資訊儲存到session中,方便以後重建subject
    **/
    public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
        //呼叫父類AuthenticationSecurityManager的authenticate方法,驗證使用者是否登陸成功
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
            //如果驗證失敗,重置cookie為初始化值
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
        //驗證失敗,則丟擲異常
            throw ae; //propagate
        }
        //建立subject
        Subject loggedIn = createSubject(token, info, subject);
    //設定唯一的principle到cookie中
        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }

    protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
        rememberMeSuccessfulLogin(token, info, subject);
    }

    protected void onFailedLogin(AuthenticationToken token, AuthenticationException ae, Subject subject) {
        rememberMeFailedLogin(token, ae, subject);
    }

    protected void beforeLogout(Subject subject) {
        rememberMeLogout(subject);
    }

    protected SubjectContext copy(SubjectContext subjectContext) {
        return new DefaultSubjectContext(subjectContext);
    }


    public Subject createSubject(SubjectContext subjectContext) {
        //複製個subjectContext,不修改原來的subjectContext
        SubjectContext context = copy(subjectContext);

        //確保context已設定securityManager
        context = ensureSecurityManager(context);

        //確保context已設定session,如果不存在想辦法獲取已存在的session,然後設定進去
        context = resolveSession(context);

        //確保context已設定Principals,如果未設定則從rememberManager獲取,然後設定
        context = resolvePrincipals(context);

    //呼叫subjectFactory建立subject
        Subject subject = doCreateSubject(context);

        //儲存subject狀態到session
        save(subject);

        return subject;
    }


    protected Subject doCreateSubject(SubjectContext context) {
        return getSubjectFactory().createSubject(context);
    }

    //呼叫subjectDAO儲存subject狀態
    protected void save(Subject subject) {
        this.subjectDAO.save(subject);
    }

    //呼叫subjectDAO刪除subject狀態
    protected void delete(Subject subject) {
        this.subjectDAO.delete(subject);
    }


    @SuppressWarnings({"unchecked"})
    protected SubjectContext ensureSecurityManager(SubjectContext context) {
        if (context.resolveSecurityManager() != null) {
            log.trace("Context already contains a SecurityManager instance.  Returning.");
            return context;
        }
        log.trace("No SecurityManager found in context.  Adding self reference.");
        context.setSecurityManager(this);
        return context;
    }

    //確保session被設定到context
    @SuppressWarnings({"unchecked"})
    protected SubjectContext resolveSession(SubjectContext context) {
    //如果已經存在session,則直接返回context
        if (context.resolveSession() != null) {
            log.debug("Context already contains a session.  Returning.");
            return context;
        }
        try {

        //根據sessionKey從sessionManager獲取session
            Session session = resolveContextSession(context);
            if (session != null) {
                context.setSession(session);
            }
        } catch (InvalidSessionException e) {
            log.debug("Resolved SubjectContext context session is invalid.  Ignoring and creating an anonymous " +
                    "(session-less) Subject instance.", e);
        }
        return context;
    }

    //根據sessionKey從sessionManager獲取session
    protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
        SessionKey key = getSessionKey(context);
        if (key != null) {
            return getSession(key);
        }
        return null;
    }

    //獲取sessionKey(SID)
    protected SessionKey getSessionKey(SubjectContext context) {
        Serializable sessionId = context.getSessionId();
        if (sessionId != null) {
            return new DefaultSessionKey(sessionId);
        }
        return null;
    }


    @SuppressWarnings({"unchecked"})
    protected SubjectContext resolvePrincipals(SubjectContext context) {

        PrincipalCollection principals = context.resolvePrincipals();

        if (CollectionUtils.isEmpty(principals)) {

        //從Remembered獲取principals
            principals = getRememberedIdentity(context);

            if (!CollectionUtils.isEmpty(principals)) {

                context.setPrincipals(principals);

            } else {
                log.trace("No remembered identity found.  Returning original context.");
            }
        }

        return context;
    }


    protected SessionContext createSessionContext(SubjectContext subjectContext) {
        DefaultSessionContext sessionContext = new DefaultSessionContext();
        if (!CollectionUtils.isEmpty(subjectContext)) {
            sessionContext.putAll(subjectContext);
        }
        Serializable sessionId = subjectContext.getSessionId();
        if (sessionId != null) {
            sessionContext.setSessionId(sessionId);
        }
        String host = subjectContext.resolveHost();
        if (host != null) {
            sessionContext.setHost(host);
        }
        return sessionContext;
    }

    //使用者退出方法
    public void logout(Subject subject) {

        if (subject == null) {
            throw new IllegalArgumentException("Subject method argument cannot be null.");
        }

    //在執行退出方法前,重置cookie為初始化值
        beforeLogout(subject);


        PrincipalCollection principals = subject.getPrincipals();
        if (principals != null && !principals.isEmpty()) {
            if (log.isDebugEnabled()) {
                log.debug("Logging out subject with primary principal {}", principals.getPrimaryPrincipal());
            }
        //呼叫驗證器的onLogout方法,目前該方法主要是迭代AuthenticationListener的logout方法
            Authenticator authc = getAuthenticator();
            if (authc instanceof LogoutAware) {
                ((LogoutAware) authc).onLogout(principals);
            }
        }

        try {
            delete(subject);
        } catch (Exception e) {

        } finally {
            try {
        //設定session過期
                stopSession(subject);
            } catch (Exception e) {

            }
        }
    } 
    //設定session過期
    protected void stopSession(Subject subject) {
        Session s = subject.getSession(false);
        if (s != null) {
            s.stop();
        }
    }

    /**
     * Unbinds or removes the Subject's state from the application, typically called during {@link #logout}.
     * <p/>
     * This has been deprecated in Shiro 1.2 in favor of the {@link #delete(org.apache.shiro.subject.Subject) delete}
     * method.  The implementation has been updated to invoke that method.
     *
     * @param subject the subject to unbind from the application as it will no longer be used.
     * @deprecated in Shiro 1.2 in favor of {@link #delete(org.apache.shiro.subject.Subject)}
     */
    @Deprecated
    @SuppressWarnings({"UnusedDeclaration"})
    protected void unbind(Subject subject) {
        delete(subject);
    }

    //根據SubjectContext從RememberMeManager獲取PrincipalCollection
    protected PrincipalCollection getRememberedIdentity(SubjectContext subjectContext) {
        RememberMeManager rmm = getRememberMeManager();
        if (rmm != null) {
            try {
                return rmm.getRememberedPrincipals(subjectContext);
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    String msg = "Delegate RememberMeManager instance of type [" + rmm.getClass().getName() +
                            "] threw an exception during getRememberedPrincipals().";
                    log.warn(msg, e);
                }
            }
        }
        return null;
    }
}

7.DefaultWebSecurityManager

//WebSecurityManager實現被使用在基於web的應用程式中或者需要http請求的應用程式中(如SOAP,http remoting, etc)
public class DefaultWebSecurityManager extends DefaultSecurityManager implements WebSecurityManager {

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

    @Deprecated
    public static final String HTTP_SESSION_MODE = "http";
    @Deprecated
    public static final String NATIVE_SESSION_MODE = "native";

    /**
     * @deprecated as of 1.2.  This should NOT be used for anything other than determining if the sessionMode has changed.
     */
    @Deprecated
    private String sessionMode;

    public DefaultWebSecurityManager() {
        super();
    //設定SessionStorageEvaluator(評估Session是否需要儲存,在無狀態環境下可以設定為false,如Rest架構)到DefaultSubjectDAO
        ((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
        this.sessionMode = HTTP_SESSION_MODE;
    //初始化基於WEB的DefaultWebSubjectFactory
        setSubjectFactory(new DefaultWebSubjectFactory());
        setRememberMeManager(new CookieRememberMeManager());
    //預設是 基於servlet的session的sessionManaer,即不使用shiro自己管理的session
        setSessionManager(new ServletContainerSessionManager());
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public DefaultWebSecurityManager(Realm singleRealm) {
        this();
        setRealm(singleRealm);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public DefaultWebSecurityManager(Collection<Realm> realms) {
        this();
        setRealms(realms);
    }

    @Override
    protected SubjectContext createSubjectContext() {
        return new DefaultWebSubjectContext();
    }

    @Override
    //since 1.2.1 for fixing SHIRO-350
    public void setSubjectDAO(SubjectDAO subjectDAO) {
        super.setSubjectDAO(subjectDAO);
    //設定sessionManager到DefaultSubjectDAO的SessionStorageEvaluator
        applySessionManagerToSessionStorageEvaluatorIfPossible();
    }

    //在sessionManager被設定後,設定sessionManager到DefaultSubjectDAO的SessionStorageEvaluator
    @Override
    protected void afterSessionManagerSet() {
        super.afterSessionManagerSet();
        applySessionManagerToSessionStorageEvaluatorIfPossible();
    }

    //設定sessionManager到DefaultSubjectDAO的SessionStorageEvaluator
    private void applySessionManagerToSessionStorageEvaluatorIfPossible() {
        SubjectDAO subjectDAO = getSubjectDAO();
        if (subjectDAO instanceof DefaultSubjectDAO) {
            SessionStorageEvaluator evaluator = ((DefaultSubjectDAO)subjectDAO).getSessionStorageEvaluator();
            if (evaluator instanceof DefaultWebSessionStorageEvaluator) {
                ((DefaultWebSessionStorageEvaluator)evaluator).setSessionManager(getSessionManager());
            }
        }
    }

    @Override
    protected SubjectContext copy(SubjectContext subjectContext) {
        if (subjectContext instanceof WebSubjectContext) {
            return new DefaultWebSubjectContext((WebSubjectContext) subjectContext);
        }
        return super.copy(subjectContext);
    }

    @SuppressWarnings({"UnusedDeclaration"})
    @Deprecated
    public String getSessionMode() {
        return sessionMode;
    }

    /**
     * @param sessionMode
     * @deprecated since 1.2
     */
    @Deprecated
    public void setSessionMode(String sessionMode) {
        log.warn("The 'sessionMode' property has been deprecated.  Please configure an appropriate WebSessionManager " +
                "instance instead of using this property.  This property/method will be removed in a later version.");
        String mode = sessionMode;
        if (mode == null) {
            throw new IllegalArgumentException("sessionMode argument cannot be null.");
        }
        mode = sessionMode.toLowerCase();
        if (!HTTP_SESSION_MODE.equals(mode) && !NATIVE_SESSION_MODE.equals(mode)) {
            String msg = "Invalid sessionMode [" + sessionMode + "].  Allowed values are " +
                    "public static final String constants in the " + getClass().getName() + " class: '"
                    + HTTP_SESSION_MODE + "' or '" + NATIVE_SESSION_MODE + "', with '" +
                    HTTP_SESSION_MODE + "' being the default.";
            throw new IllegalArgumentException(msg);
        }
        boolean recreate = this.sessionMode == null || !this.sessionMode.equals(mode);
        this.sessionMode = mode;
        if (recreate) {
            LifecycleUtils.destroy(getSessionManager());
            SessionManager sessionManager = createSessionManager(mode);
            this.setInternalSessionManager(sessionManager);
        }
    }

    @Override
    public void setSessionManager(SessionManager sessionManager) {
        this.sessionMode = null;
        if (sessionManager != null && !(sessionManager instanceof WebSessionManager)) {
            if (log.isWarnEnabled()) {
                String msg = "The " + getClass().getName() + " implementation expects SessionManager instances " +
                        "that implement the " + WebSessionManager.class.getName() + " interface.  The " +
                        "configured instance is of type [" + sessionManager.getClass().getName() + "] which does not " +
                        "implement this interface..  This may cause unexpected behavior.";
                log.warn(msg);
            }
        }
        setInternalSessionManager(sessionManager);
    }

    /**
     * @param sessionManager
     * @since 1.2
     */
    private void setInternalSessionManager(SessionManager sessionManager) {
        super.setSessionManager(sessionManager);
    }

    //判斷是否是使用httpSession
    public boolean isHttpSessionMode() {
        SessionManager sessionManager = getSessionManager();
        return sessionManager instanceof WebSessionManager && ((WebSessionManager)sessionManager).isServletContainerSessions();
    }

    protected SessionManager createSessionManager(String sessionMode) {
        if (sessionMode == null || !sessionMode.equalsIgnoreCase(NATIVE_SESSION_MODE)) {
            log.info("{} mode - enabling ServletContainerSessionManager (HTTP-only Sessions)", HTTP_SESSION_MODE);
            return new ServletContainerSessionManager();
        } else {
            log.info("{} mode - enabling DefaultWebSessionManager (non-HTTP and HTTP Sessions)", NATIVE_SESSION_MODE);
            return new DefaultWebSessionManager();
        }
    }

    @Override
    protected SessionContext createSessionContext(SubjectContext subjectContext) {
        SessionContext sessionContext = super.createSessionContext(subjectContext);
        if (subjectContext instanceof WebSubjectContext) {
            WebSubjectContext wsc = (WebSubjectContext) subjectContext;
            ServletRequest request = wsc.resolveServletRequest();
            ServletResponse response = wsc.resolveServletResponse();
            DefaultWebSessionContext webSessionContext = new DefaultWebSessionContext(sessionContext);
            if (request != null) {
                webSessionContext.setServletRequest(request);
            }
            if (response != null) {
                webSessionContext.setServletResponse(response);
            }

            sessionContext = webSessionContext;
        }
        return sessionContext;
    }

    @Override
    protected SessionKey getSessionKey(SubjectContext context) {
        if (WebUtils.isWeb(context)) {
            Serializable sessionId = context.getSessionId();
            ServletRequest request = WebUtils.getRequest(context);
            ServletResponse response = WebUtils.getResponse(context);
            return new WebSessionKey(sessionId, request, response);
        } else {
            return super.getSessionKey(context);

        }
    }

    @Override
    protected void beforeLogout(Subject subject) {
        super.beforeLogout(subject);
        removeRequestIdentity(subject);
    }

    protected void removeRequestIdentity(Subject subject) {
        if (subject instanceof WebSubject) {
            WebSubject webSubject = (WebSubject) subject;
            ServletRequest request = webSubject.getServletRequest();
            if (request != null) {
                request.setAttribute(ShiroHttpServletRequest.IDENTITY_REMOVED_KEY, Boolean.TRUE);
            }
        }
    }
}