千家信息网

Apache Shiro源码解读之Subject的创建

发表于:2024-12-13 作者:千家信息网编辑
千家信息网最后更新 2024年12月13日,Subject是Shiro中十分重要的对象,可以简单的理解为"当前用户"。 首先来看下Subject的继承关系不论是web应用程序还是普通应用程序,我们在某个方法里面都已通过以下方法来获取Subjec
千家信息网最后更新 2024年12月13日Apache Shiro源码解读之Subject的创建

Subject是Shiro中十分重要的对象,可以简单的理解为"当前用户"。 首先来看下Subject的继承关系

不论是web应用程序还是普通应用程序,我们在某个方法里面都已通过以下方法来获取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的内部内来创建,然后再绑定到ThreadContext上。那么我们接着看看ThreadContext的定义

public abstract class ThreadContext {    public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";    private static final ThreadLocal> resources = new InheritableThreadLocalMap>();    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 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创建Subject的时候是委托给SecurityManager来创建的,而SecurityManager又是从SecurityUtils从返回。那么还得追溯下SecurityManager是如何被创建的才能进一步得知Subject的创建。而根据Subject的继承关系图可知,它本身只是个接口,那么其实现类又该对应的哪个,如何判定应该使用哪个?

对于在web环境中集成shrio时,一般是在web.xml文件中添加以下的配置

        org.apache.shiro.web.env.EnvironmentLoaderListener        ShiroFilter        org.apache.shiro.web.servlet.ShiroFilter        ShiroFilter        /*        REQUEST        FORWARD        INCLUDE        ERROR

既然是在请求的过程中获取并使用的Subject, 那我们就来看看ShiroFilter类都包含了哪些内容,首先看看ShiroFilter的继承关系

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方法,看名字应该是初始化给Filter时候运行的,那么在何处调用的,我们继续看看他的父类

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();  //调用了子类【ShiroFilter】的init()方法(开始得到WebEnvironment等对象)        ensureSecurityManager();  //确认SecurityManager是否存在,不存在则创建默认的DefaultWebSecurityManager对象        //added in 1.2 for SHIRO-287:        if (isStaticSecurityManagerEnabled()) {            /*                        注意:这里很重要,在WEB环境是不建议将SecurityManager对象保存在静态变量中的。。。                        根据Filter配置的初始化参数判断是否要将SecurityManager通过SecurityUtils当做静态变量进行保存                    */          SecurityUtils.setSecurityManager(getSecurityManager());        }    }}

可知AbstractFilter调用了AbstractShiroFilter,然后再调用了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对象的创建过程。我们就得回到 到以下对象上

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);  //注意:这了会调用environment的init方法来初始化environment        return environment;    }    protected WebEnvironment determineWebEnvironment(ServletContext servletContext) {        Class 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 getDefaultWebEnvironmentClass() {        return IniWebEnvironment.class;    }}

经过一路的奔波,最终创建了默认的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() {        //解析指定或默认位置的配置文件并生成对应的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,并构建SecurityManager对象        */    protected WebSecurityManager createWebSecurityManager() {        Ini ini = getIni();        if (!CollectionUtils.isEmpty(ini)) {            factory.setIni(ini);        }        Map 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 beans = factory.getBeans();        if (!CollectionUtils.isEmpty(beans)) {            this.objects.putAll(beans);        }        return wsm;   }    protected Map getDefaults() {        Map defaults = new HashMap();        defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory());        return defaults;   }}

接着继续跟踪WebIniSecurityManagerFactory的执行

public class WebIniSecurityManagerFactory extends IniSecurityManagerFactory {    protected SecurityManager createDefaultInstance() {      return new DefaultWebSecurityManager();  }}

附SecurityManager继承关系,后面再详细解析SecurityManager

HTTP请求处理过程
1,每个http请求都被ShoriFilter拦截进行处理
2,将SecurityManager对象和包装后的Request和Response作为构造参数创建WebSubject.Builder实例,并调用buildWebSubject方法创建Subject
3,在构造方法中创新新的SubjectContext实例,并将SecurityManager保存到SubjectContext实例中
4,将Request和Response也添加到SubjectContext中保存
5,将subjectContext作为参数,调用SecurityManager的createSubject方法创建Subject对象
6,将SubjectContext作为参数,调用SubjectFactory【DefaultSubjectFactory】的createSubject方法创建Subject
7,接着取出SubjectContext一路收集来的数据来构建DelegatingSubject对象并返回。
8,当调用Subject的getSession方法的时候,如果Session不存在,则首先创建一个新的DefaultSessionContext实例并设置host值【可能是空】
9,将sessionContext对象作为参数调用securityManager的start方法来创建Session
10,从SessionContext中取出HttpServletRequest,并调用HttpServletRequest的getSession方法来获取HttpSession,同时从SessionContext中取出host,使用这两个值作为构造函数的参数实例化HttpServletSession类。
11,到此,Session的创建过程结束,此时的HttpServletSession纯粹只是HttpSession的代理一样。

0