千家信息网

Apache Shiro源码解读之Subject的创建

发表于:2025-12-01 作者:千家信息网编辑
千家信息网最后更新 2025年12月01日,Subject是Shiro中十分重要的对象,可以简单的理解为"当前用户"。 首先来看下Subject的继承关系不论是web应用程序还是普通应用程序,我们在某个方法里面都已通过以下方法来获取Subjec
千家信息网最后更新 2025年12月01日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的代理一样。

对象 方法 参数 实例 过程 时候 用户 配置 重要 接下来 代码 变量 只是 应用程序 文件 是在 环境 程序 静态 处理 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 查找我国贸易内容数据库 如何连接到网站服务器 宿迁企业软件开发创新服务 参加工作软件开发电脑配置 数据库与系统概论课后答案 软件开发领域有哪些变化 2021年网络安全等保目录 百度服务器怎么样 网络安全事故报告表 node服务器框架 软件工程网络软件开发 普陀区节能软件开发服务结构设计 物联网的网络安全是哪一层 数据库管理系统的 小米摄像头存储服务器 项目同步到主数据库 上海汽车充电桩软件开发 软件开发涉密十二条管理制度 导航系统数据库版本途昂 东方口岸安全数据库用途 苏州天成网络技术有限公司 数据库的控制语言语句 云锁服务器怎么检查安全 东城区大型软件开发规定 网络安全手抄报大图简单 闵行区互联网软件开发诚信经营 动物数据库数据下载 企业网络安全手抄报模板 网络安全大赛教学视频 贵广网络技术怎么样
0