温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Apache Shiro源码解读之Subject的创建

发布时间:2020-07-24 03:01:42 来源:网络 阅读:1737 作者:mybabe0312 栏目:软件技术

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

Apache 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<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创建Subject的时候是委托给SecurityManager来创建的,而SecurityManager又是从SecurityUtils从返回。那么还得追溯下SecurityManager是如何被创建的才能进一步得知Subject的创建。而根据Subject的继承关系图可知,它本身只是个接口,那么其实现类又该对应的哪个,如何判定应该使用哪个?

对于在web环境中集成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类都包含了哪些内容,首先看看ShiroFilter的继承关系
Apache Shiro源码解读之Subject的创建

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<? 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;
    }

}

经过一路的奔波,最终创建了默认的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<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;
   }

}

接着继续跟踪WebIniSecurityManagerFactory的执行

public class WebIniSecurityManagerFactory extends IniSecurityManagerFactory {

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

附SecurityManager继承关系,后面再详细解析SecurityManager
Apache Shiro源码解读之Subject的创建

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的代理一样。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI