溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Apache Shiro源碼解讀之Subject的創(chuàng)建

發(fā)布時間:2020-07-24 03:01:42 來源:網(wǎng)絡(luò) 閱讀:1737 作者:mybabe0312 欄目:軟件技術(shù)

Subject是Shiro中十分重要的對象,可以簡單的理解為“當前用戶”。 首先來看下Subject的繼承關(guān)系

Apache Shiro源碼解讀之Subject的創(chuàng)建

不論是web應(yīng)用程序還是普通應(yīng)用程序,我們在某個方法里面都已通過以下方法來獲取Subject對象并使用Session

Subject currentUser = org.apache.shiro.SecurityUtils.getSubject();
if ( !currentUser.isAuthenticated() ) {
            UsernamePasswordToken token = new UsernamePasswordToken("user", "password");
            token.setRememberMe(true);
            try {
                currentUser.login( token );
                Session session = currentUser.getSession();
                session.setAttribute( "key", "value" );
            } catch ( UnknownAccountException uae ) {
                //用戶不存在
            } catch ( IncorrectCredentialsException ice ) {
                //密碼錯誤
            } catch ( LockedAccountException lae ) {
                //用戶被鎖
            } catch ( AuthenticationException ae ) {
                //unexpected condition - error?
            }
}

該SecurityUtils位于shiro-core.jar中

簡單的兩句代碼就可以完成登錄驗證,也可以使用Session,看起來十分簡單,可簡單的背后可能又隱藏著許多復雜之處,接下來我們就來一探究竟。

/**
 * Accesses the currently accessible {@code Subject} for the calling code depending on runtime environment.
 *
 * @since 0.2
 */
public abstract class SecurityUtils {

    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }
}

Subject首先直接從TreadContext里面直接獲取,如果沒有獲取到則使用Subject的內(nèi)部內(nèi)來創(chuàng)建,然后再綁定到ThreadContext上。那么我們接著看看ThreadContext的定義

public abstract class ThreadContext {
    public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

    public static Subject getSubject() {
            return (Subject) get(SUBJECT_KEY);
    }
    public static Object get(Object key) {
        if (log.isTraceEnabled()) {
            String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }
        Object value = getValue(key);
        if ((value != null) && log.isTraceEnabled()) {
            String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }
        return value;
   }
private static Object getValue(Object key) {
        Map<Object, Object> perThreadResources = resources.get();
        return perThreadResources != null ? perThreadResources.get(key) : null;
   }
}

ThreadContext里面維持著一個LocalThread對象,可見Subject是與當前線程相綁定的。

public interface Subject {
    public static class Builder {
        private final SubjectContext subjectContext;
        private final SecurityManager securityManager;

        public Builder() {
            this(SecurityUtils.getSecurityManager());
        }
        public Builder(SecurityManager securityManager) {
            if (securityManager == null) {
                    throw new NullPointerException("SecurityManager method argument cannot be null.");
            }
            this.securityManager = securityManager;
            this.subjectContext = newSubjectContextInstance();
            if (this.subjectContext == null) {
                    throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " +
                                                    "cannot be null.");
            }
            this.subjectContext.setSecurityManager(securityManager);
        }
        protected SubjectContext newSubjectContextInstance() {
            return new DefaultSubjectContext();
        }
        public Subject buildSubject() {
            return this.securityManager.createSubject(this.subjectContext);
        }
    }
}

至此,Builder創(chuàng)建Subject的時候是委托給SecurityManager來創(chuàng)建的,而SecurityManager又是從SecurityUtils從返回。那么還得追溯下SecurityManager是如何被創(chuàng)建的才能進一步得知Subject的創(chuàng)建。而根據(jù)Subject的繼承關(guān)系圖可知,它本身只是個接口,那么其實現(xiàn)類又該對應(yīng)的哪個,如何判定應(yīng)該使用哪個?

對于在web環(huán)境中集成shrio時,一般是在web.xml文件中添加以下的配置

<listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>

<filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
        <filter-name>ShiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
</filter-mapping>

既然是在請求的過程中獲取并使用的Subject, 那我們就來看看ShiroFilter類都包含了哪些內(nèi)容,首先看看ShiroFilter的繼承關(guān)系
Apache Shiro源碼解讀之Subject的創(chuàng)建

public class ShiroFilter extends AbstractShiroFilter {
    @Override
    public void init() throws Exception {

        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());

        setSecurityManager(env.getWebSecurityManager());

        FilterChainResolver resolver = env.getFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }
}

在這里之看到了init方法,看名字應(yīng)該是初始化給Filter時候運行的,那么在何處調(diào)用的,我們繼續(xù)看看他的父類

public abstract class AbstractFilter extends ServletContextSupport implements Filter {

    public final void init(FilterConfig filterConfig) throws ServletException {
        setFilterConfig(filterConfig);
        try {
            onFilterConfigSet();
        } catch (Exception e) {
                if (e instanceof ServletException) {
                        throw (ServletException) e;
                } else {
                    if (log.isErrorEnabled()) {
                            log.error("Unable to start Filter: [" + e.getMessage() + "].", e);
                    }
                    throw new ServletException(e);
                }
        }
    }
}
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
    protected final void onFilterConfigSet() throws Exception {
        //added in 1.2 for SHIRO-287:
        applyStaticSecurityManagerEnabledConfig(); 

        init();  //調(diào)用了子類【ShiroFilter】的init()方法(開始得到WebEnvironment等對象)

        ensureSecurityManager();  //確認SecurityManager是否存在,不存在則創(chuàng)建默認的DefaultWebSecurityManager對象
        //added in 1.2 for SHIRO-287:
        if (isStaticSecurityManagerEnabled()) {
            /*
                        注意:這里很重要,在WEB環(huán)境是不建議將SecurityManager對象保存在靜態(tài)變量中的。。。

                        根據(jù)Filter配置的初始化參數(shù)判斷是否要將SecurityManager通過SecurityUtils當做靜態(tài)變量進行保存
                    */
          SecurityUtils.setSecurityManager(getSecurityManager());
        }
    }
}

可知AbstractFilter調(diào)用了AbstractShiroFilter,然后再調(diào)用了ShiroFilter的init方法。 init方法的目的就是為了獲得WebEnvironment對象,其WebUtils里的代碼就簡單了,就是從ServletContext中直接獲取WebEnvironment對象,如果為空,則會拋出異常。

public class WebUtils {
    public static WebEnvironment getRequiredWebEnvironment(ServletContext sc)
            throws IllegalStateException {

        WebEnvironment we = getWebEnvironment(sc);
        if (we == null) {
            throw new IllegalStateException("No WebEnvironment found: no EnvironmentLoaderListener registered?");
        }
        return we;
    }
    public static WebEnvironment getWebEnvironment(ServletContext sc) {
        return getWebEnvironment(sc, EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY);
    }
    public static WebEnvironment getWebEnvironment(ServletContext sc, String attrName) {
        if (sc == null) {
            throw new IllegalArgumentException("ServletContext argument must not be null.");
        }
        Object attr = sc.getAttribute(attrName);
        if (attr == null) {
            return null;
        }
        if (attr instanceof RuntimeException) {
            throw (RuntimeException) attr;
        }
        if (attr instanceof Error) {
            throw (Error) attr;
        }
        if (attr instanceof Exception) {
            throw new IllegalStateException((Exception) attr);
        }
        if (!(attr instanceof WebEnvironment)) {
            throw new IllegalStateException("Context attribute is not of type WebEnvironment: " + attr);
        }
        return (WebEnvironment) attr;
    }
}

接著我們看下WebEnvironment的定義:

public interface Environment {

    /**
     * Returns the application's {@code SecurityManager} instance.
     *
     * @return the application's {@code SecurityManager} instance.
     */
    SecurityManager getSecurityManager();
}

public interface WebEnvironment extends Environment {

    /**
     * Returns the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
     * is not available.
     *
     * @return the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one
     *         is not available.
     */
    FilterChainResolver getFilterChainResolver();

    /**
     * Returns the {@code ServletContext} associated with this {@code WebEnvironment} instance.  A web application
     * typically only has a single {@code WebEnvironment} associated with its {@code ServletContext}.
     *
     * @return the {@code ServletContext} associated with this {@code WebEnvironment} instance.
     */
    ServletContext getServletContext();

    /**
     * Returns the web application's security manager instance.
     *
     * @return the web application's security manager instance.
     */
    WebSecurityManager getWebSecurityManager();
}

在WebEnvironment里面直接保存了全局唯一的SecurityManager對象。接下來我們需要追蹤SecurityManager對象的創(chuàng)建過程。我們就得回到 到以下對象上

org.apache.shiro.web.env.EnvironmentLoaderListener
public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener {

    /**
     * Initializes the Shiro {@code WebEnvironment} and binds it to the {@code ServletContext} at application
     * startup for future reference.
     *
     * @param sce the ServletContextEvent triggered upon application startup
     */
    public void contextInitialized(ServletContextEvent sce) {
        initEnvironment(sce.getServletContext());
    }

    /**
     * Destroys any previously created/bound {@code WebEnvironment} instance created by
     * the {@link #contextInitialized(javax.servlet.ServletContextEvent)} method.
     *
     * @param sce the ServletContextEvent triggered upon application shutdown
     */
    public void contextDestroyed(ServletContextEvent sce) {
        destroyEnvironment(sce.getServletContext());
    }
}

public class EnvironmentLoader {

    public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY";

    public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {

        if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) {
            String msg = "There is already a Shiro environment associated with the current ServletContext.  " +
                    "Check if you have multiple EnvironmentLoader* definitions in your web.xml!";
            throw new IllegalStateException(msg);
        }

        servletContext.log("Initializing Shiro environment");
        log.info("Starting Shiro environment initialization.");

        long startTime = System.currentTimeMillis();

        try {

            WebEnvironment environment = createEnvironment(servletContext);
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY,environment);

            log.debug("Published WebEnvironment as ServletContext attribute with name [{}]",
                    ENVIRONMENT_ATTRIBUTE_KEY);

            if (log.isInfoEnabled()) {
                long elapsed = System.currentTimeMillis() - startTime;
                log.info("Shiro environment initialized in {} ms.", elapsed);
            }

            return environment;
        } catch (RuntimeException ex) {
            log.error("Shiro environment initialization failed", ex);
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex);
            throw ex;
        } catch (Error err) {
            log.error("Shiro environment initialization failed", err);
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err);
            throw err;
        }
    }

    protected WebEnvironment createEnvironment(ServletContext sc) {

        WebEnvironment webEnvironment = determineWebEnvironment(sc);
        if (!MutableWebEnvironment.class.isInstance(webEnvironment)) {
            throw new ConfigurationException("Custom WebEnvironment class [" + webEnvironment.getClass().getName() +
                    "] is not of required type [" + MutableWebEnvironment.class.getName() + "]");
        }

        String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
        boolean configSpecified = StringUtils.hasText(configLocations);

        if (configSpecified && !(ResourceConfigurable.class.isInstance(webEnvironment))) {
            String msg = "WebEnvironment class [" + webEnvironment.getClass().getName() + "] does not implement the " +
                    ResourceConfigurable.class.getName() + "interface.  This is required to accept any " +
                    "configured " + CONFIG_LOCATIONS_PARAM + "value(s).";
            throw new ConfigurationException(msg);
        }

        MutableWebEnvironment environment = (MutableWebEnvironment) webEnvironment;

        environment.setServletContext(sc);

        if (configSpecified && (environment instanceof ResourceConfigurable)) {
            ((ResourceConfigurable) environment).setConfigLocations(configLocations);
        }

        customizeEnvironment(environment);

        LifecycleUtils.init(environment);  //注意:這了會調(diào)用environment的init方法來初始化environment

        return environment;
    }

    protected WebEnvironment determineWebEnvironment(ServletContext servletContext) {

        Class<? extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext);
        WebEnvironment webEnvironment = null;

        // try service loader next
        if (webEnvironmentClass == null) {
            webEnvironment = webEnvironmentFromServiceLoader();
        }

        // if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default
        if (webEnvironmentClass == null && webEnvironment == null) {
            webEnvironmentClass = getDefaultWebEnvironmentClass();
        }

        // at this point, we anything is set for the webEnvironmentClass, load it.
        if (webEnvironmentClass != null) {
            webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass);
        }

        return webEnvironment;
    }

    protected Class<? extends WebEnvironment> getDefaultWebEnvironmentClass() {
        return IniWebEnvironment.class;
    }

}

經(jīng)過一路的奔波,最終創(chuàng)建了默認的Environment對象IniWebEnvironment。

接著我們再看看IniWebEnvironment對象初始化都做了些啥事

public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {

    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
    public static final String FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver";

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

    /**
     * The Ini that configures this WebEnvironment instance.
     */
    private Ini ini;

    private WebIniSecurityManagerFactory factory;

    public IniWebEnvironment() {
        factory = new WebIniSecurityManagerFactory();
    }

    public void init() {
        //解析指定或默認位置的配置文件并生成對應(yīng)的Ini對象
        setIni(parseConfig());

        configure();
   }

    protected void configure() {

        this.objects.clear();

        WebSecurityManager securityManager = createWebSecurityManager();
        setWebSecurityManager(securityManager);

        FilterChainResolver resolver = createFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
   }

    /*
        將Ini對象傳遞給WebIniSecurityManagerFactory,并構(gòu)建SecurityManager對象
        */
    protected WebSecurityManager createWebSecurityManager() {

        Ini ini = getIni();
        if (!CollectionUtils.isEmpty(ini)) {
            factory.setIni(ini);
        }

        Map<String, Object> defaults = getDefaults();
        if (!CollectionUtils.isEmpty(defaults)) {
            factory.setDefaults(defaults);
        }

        WebSecurityManager wsm = (WebSecurityManager)factory.getInstance();

        //SHIRO-306 - get beans after they've been created (the call was before the factory.getInstance() call,
        //which always returned null.
        Map<String, ?> beans = factory.getBeans();
        if (!CollectionUtils.isEmpty(beans)) {
            this.objects.putAll(beans);
        }

        return wsm;
   }

    protected Map<String, Object> getDefaults() {
        Map<String, Object> defaults = new HashMap<String, Object>();
        defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory());
        return defaults;
   }

}

接著繼續(xù)跟蹤WebIniSecurityManagerFactory的執(zhí)行

public class WebIniSecurityManagerFactory extends IniSecurityManagerFactory {

    protected SecurityManager createDefaultInstance() {
      return new DefaultWebSecurityManager();
  }
}

附SecurityManager繼承關(guān)系,后面再詳細解析SecurityManager
Apache Shiro源碼解讀之Subject的創(chuàng)建

HTTP請求處理過程
1,每個http請求都被ShoriFilter攔截進行處理
2,將SecurityManager對象和包裝后的Request和Response作為構(gòu)造參數(shù)創(chuàng)建WebSubject.Builder實例,并調(diào)用buildWebSubject方法創(chuàng)建Subject
3,在構(gòu)造方法中創(chuàng)新新的SubjectContext實例,并將SecurityManager保存到SubjectContext實例中
4,將Request和Response也添加到SubjectContext中保存
5,將subjectContext作為參數(shù),調(diào)用SecurityManager的createSubject方法創(chuàng)建Subject對象
6,將SubjectContext作為參數(shù),調(diào)用SubjectFactory【DefaultSubjectFactory】的createSubject方法創(chuàng)建Subject
7,接著取出SubjectContext一路收集來的數(shù)據(jù)來構(gòu)建DelegatingSubject對象并返回。
8,當調(diào)用Subject的getSession方法的時候,如果Session不存在,則首先創(chuàng)建一個新的DefaultSessionContext實例并設(shè)置host值【可能是空】
9,將sessionContext對象作為參數(shù)調(diào)用securityManager的start方法來創(chuàng)建Session
10,從SessionContext中取出HttpServletRequest,并調(diào)用HttpServletRequest的getSession方法來獲取HttpSession,同時從SessionContext中取出host,使用這兩個值作為構(gòu)造函數(shù)的參數(shù)實例化HttpServletSession類。
11,到此,Session的創(chuàng)建過程結(jié)束,此時的HttpServletSession純粹只是HttpSession的代理一樣。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI