shrio web第一天

要想在javaweb项目里面用到shrio,先要配置如下:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   xsi:schemaLocation="http://java.sun.com/xml/ns/javaehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    <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>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

可以看出shrio配置了一个监听器和一个拦截器,难道这样就可以实现shrio认证了?

我们先来看一下EnvironmentLoaderListener这个类的架构:实现了ServletContextListener接口

  1. EventListener 是一个标志接口,里面没有任何的方法,Servlet 容器中所有的 Listener 都要继承这个接口(这是 Servlet 规范)。

  2. ServletContextListener 是一个 ServletContext 的监听器,用于监听容器的启动与关闭事件,包括如下两个方法:

    • void contextInitialized(ServletContextEvent sce); // 当容器启动时调用
    • void contextDestroyed(ServletContextEvent sce); // 当容器关闭时调用
      可以从 ServletContextEvent 中直接获取 ServletContext 对象。
  3. EnvironmentLoaderListener 不仅实现了 ServletContextListener 接口,也扩展了 EnvironmentLoader 类,看来它是想在 Servlet 容器中调用 EnvironmentLoader 对象的生命周期方法。

毫无疑问,我们首先从 EnvironmentLoaderListener 开始:

public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener {
 
    // 容器启动时调用
    public void contextInitialized(ServletContextEvent sce) {
        initEnvironment(sce.getServletContext());
    }
 
    // 当容器关闭时调用
    public void contextDestroyed(ServletContextEvent sce) {
        destroyEnvironment(sce.getServletContext());
    }
}

看来这个类不是干活的,只是调用了它的父类EnvironmentLoader方法

这个类的架构如下:

public class EnvironmentLoader {
 
    // 可在 web.xml 的 context-param 中定义 WebEnvironment 接口的实现类(默认为 IniWebEnvironment)
    public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";
 
    // 可在 web.xml 的 context-param 中定义 Shiro 配置文件的位置
    public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations";
 
    // 在 ServletContext 中存放 WebEnvironment 的 key
    public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY";
 
    // 从 ServletContext 中获取相关信息,并创建 WebEnvironment 实例
    public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException {
        // 确保 WebEnvironment 只能创建一次,并且每个Servlet只能有一个WebEnvironment,key为成员变量ENVIRONMENT_ATTRIBUTE_KEY的值
        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();
     }
        try {
            // 创建 WebEnvironment 实例
            WebEnvironment environment = createEnvironment(servletContext);
 
            // 将 WebEnvironment 实例放入 ServletContext 中
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment);
            return environment;
        } catch (RuntimeException ex) {
            // 将异常对象放入 ServletContext 中
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex);
            throw ex;
        } catch (Error err) {
            // 将错误对象放入 ServletContext 中
            servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err);
            throw err;
        }
    }
 
    protected WebEnvironment createEnvironment(ServletContext sc) {
        // 确定 WebEnvironment 接口的实现类
        Class<?> clazz = determineWebEnvironmentClass(sc);
 
        // 确保该实现类实现了 MutableWebEnvironment 接口,自定义的WebEnvironment必须是MutableWebEnvironment的实现类,否则会抛出异常
        if (!MutableWebEnvironment.class.isAssignableFrom(clazz)) {
            throw new ConfigurationException();
        }
 
        // 从 ServletContext 中获取 Shiro 配置文件的位置参数,并判断该参数是否已定义
        String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM);
        boolean configSpecified = StringUtils.hasText(configLocations);
 
        // 若配置文件位置参数已定义,则需确保该实现类实现了 ResourceConfigurable 接口
        //key为成员变量CONFIG_LOCATIONS_PARAM的值
        if (configSpecified && !(ResourceConfigurable.class.isAssignableFrom(clazz))) {
            throw new ConfigurationException();
        }
 
        // 通过反射创建 WebEnvironment 实例,将其转型为 MutableWebEnvironment 类型,并将 ServletContext 放入该实例中
     //为什么不是放到ServletContext里面?
     // servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, environment);
     //可以看到上面,就是互相包含的意思
        MutableWebEnvironment environment = (MutableWebEnvironment) ClassUtils.newInstance(clazz);
        environment.setServletContext(sc);
 
        // 若配置文件位置参数已定义,且该实例是 ResourceConfigurable 接口的实例(实现了该接口),则将此参数放入该实例中
        if (configSpecified && (environment instanceof ResourceConfigurable)) {
            ((ResourceConfigurable) environment).setConfigLocations(configLocations);
        }
 
        // 可进一步定制 WebEnvironment 实例(在子类中扩展)
        customizeEnvironment(environment);
        // 调用 WebEnvironment 实例的 init 方法
        //可以知道自定义WebEnvironment必须实现org.apache.shiro.util.Initializable接口
        LifecycleUtils.init(environment);
 
        // 返回 WebEnvironment 实例
        return environment;
    }
 
    protected Class<?> determineWebEnvironmentClass(ServletContext servletContext) {
        // 从初始化参数(context-param)中获取 WebEnvironment 接口的实现类
        //因此我们可以在这里定义自己的WebEnvironment实现类,key为              //shiroEnvironmentClass
        String className = servletContext.getInitParameter(ENVIRONMENT_CLASS_PARAM);
        // 若该参数已定义,则加载该实现类
        if (className != null) {
            try {
                return ClassUtils.forName(className);
            } catch (UnknownClassException ex) {
                throw new ConfigurationException(ex);
            }
        } else {
            // 否则使用默认的实现类
            return IniWebEnvironment.class;
        }
    }
 
    protected void customizeEnvironment(WebEnvironment environment) {
    }
 
    // 销毁 WebEnvironment 实例
    public void destroyEnvironment(ServletContext servletContext) {
        try {
            // 从 ServletContext 中获取 WebEnvironment 实例
            Object environment = servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
            // 调用 WebEnvironment 实例的 destroy 方法
            LifecycleUtils.destroy(environment);
        } finally {
            // 移除 ServletContext 中存放的 WebEnvironment 实例
            servletContext.removeAttribute(ENVIRONMENT_ATTRIBUTE_KEY);
        }
    }
}


看来 EnvironmentLoader 就是为了:

  1. 当容器启动时,读取 web.xml 文件,从中获取 WebEnvironment 接口的实现类(默认是 IniWebEnvironment),初始化该实例,并将其加载到 ServletContext 中。

    扫描二维码关注公众号,回复: 11347357 查看本文章
  2. 当容器关闭时,销毁 WebEnvironment 实例,并从 ServletContext 将其移除。

这里有两个配置项可以在 web.xml 中进行配置:

<context-param>
    <param-name>shiroEnvironmentClass</param-name>
    <param-value>WebEnvironment 接口的实现类</param-value>
</context-param>
<context-param>
    <param-name>shiroConfigLocations</param-name>
    <param-value>shiro.ini 配置文件的位置</param-value>
</context-param>

我们先来看一下IniWebEnvironment的结构:
在这里插入图片描述
可以看出IniWebEnvironment的架构很复杂啊,回到EnvironmentLoader,建立WebEnvironment对象的过程中,会进行初始化:LifecycleUtils.init(environment);可以看出这会调用WebEnvironment的初始化方法,下面我们打开IniWebEnvironment的源码,去看init()这个方法

public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
 
    // 默认 shiro.ini 路径
    public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini";
 
    // 定义一个 Ini 对象,用于封装 ini 配置项
    private Ini ini;
 
    public Ini getIni() {
        return this.ini;
    }
 
    public void setIni(Ini ini) {
        this.ini = ini;
    }
 
    // 当初始化时调用
    public void init() {
        // 从成员变量中获取 Ini 对象
        Ini ini = getIni();
 
        // 从 web.xml 中获取配置文件位置(在 EnvironmentLoader 中已设置)
        String[] configLocations = getConfigLocations();
 
        // 若成员变量中不存在,则从已定义的配置文件位置获取
        if (CollectionUtils.isEmpty(ini)) {
            ini = getSpecifiedIni(configLocations);
        }
 
        // 若已定义的配置文件中仍然不存在,则从默认的位置获取
        if (CollectionUtils.isEmpty(ini)) {
            ini = getDefaultIni();
        }
 
        // 若还不存在,则抛出异常
        if (CollectionUtils.isEmpty(ini)) {
            throw new ConfigurationException();
        }
 
        // 初始化成员变量
        setIni(ini);
 
        // 解析配置文件,完成初始化工作
        configure();
    }
 
    protected Ini getSpecifiedIni(String[] configLocations) throws ConfigurationException {
        Ini ini = null;
        if (configLocations != null && configLocations.length > 0) {
            // 只能通过第一个配置文件的位置来创建 Ini 对象,且必须有一个配置文件,否则就会报错
            ini = createIni(configLocations[0], true);
        }
        return ini;
    }
 
    protected Ini createIni(String configLocation, boolean required) throws ConfigurationException {
        Ini ini = null;
        if (configLocation != null) {
            // 从指定路径下读取配置文件
            ini = convertPathToIni(configLocation, required);
        }
        if (required && CollectionUtils.isEmpty(ini)) {
            throw new ConfigurationException();
        }
        return ini;
    }
 
    private Ini convertPathToIni(String path, boolean required) {
        Ini ini = null;
        if (StringUtils.hasText(path)) {
            InputStream is = null;
            // 若路径不包括资源前缀(classpath:、url:、file:),则从 ServletContext 中读取,否则从这些资源路径下读取
            if (!ResourceUtils.hasResourcePrefix(path)) {
                is = getServletContextResourceStream(path);
            } else {
                try {
                    is = ResourceUtils.getInputStreamForPath(path);
                } catch (IOException e) {
                    if (required) {
                        throw new ConfigurationException(e);
                    }
                }
            }
            // 将流中的数据加载到 Ini 对象中
            if (is != null) {
                ini = new Ini();
                ini.load(is);
            } else {
                if (required) {
                    throw new ConfigurationException();
                }
            }
        }
        return ini;
    }
 
    private InputStream getServletContextResourceStream(String path) {
        InputStream is = null;
        // 需要将路径进行标准化
        path = WebUtils.normalize(path);
        ServletContext sc = getServletContext();
        if (sc != null) {
            is = sc.getResourceAsStream(path);
        }
        return is;
    }
 
    protected Ini getDefaultIni() {
        Ini ini = null;
        String[] configLocations = getDefaultConfigLocations();
        if (configLocations != null) {
            // 先找到的先使用,后面的无需使用
            for (String location : configLocations) {
                ini = createIni(location, false);
                if (!CollectionUtils.isEmpty(ini)) {
                    break;
                }
            }
        }
        return ini;
    }
 
    protected String[] getDefaultConfigLocations() {
        return new String[]{
            DEFAULT_WEB_INI_RESOURCE_PATH,              // /WEB-INF/shiro.ini
            IniFactorySupport.DEFAULT_INI_RESOURCE_PATH // classpath:shiro.ini
        };
    }
 
    protected void configure() {
        // 清空这个 Bean 容器(一个 Map<String, Object> 对象,在 DefaultEnvironment 中定义)
        this.objects.clear();
 
        // 创建基于 Web 的 SecurityManager 对象(WebSecurityManager)
        WebSecurityManager securityManager = createWebSecurityManager();
        setWebSecurityManager(securityManager);
 
        // 初始化 Filter Chain 解析器(用于解析 Filter 规则)
        FilterChainResolver resolver = createFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }
 
    protected WebSecurityManager createWebSecurityManager() {
        // 通过工厂对象来创建 WebSecurityManager 实例
        WebIniSecurityManagerFactory factory;
        Ini ini = getIni();
        if (CollectionUtils.isEmpty(ini)) {
            factory = new WebIniSecurityManagerFactory();
        } else {
            factory = new WebIniSecurityManagerFactory(ini);
        }
        WebSecurityManager wsm = (WebSecurityManager) factory.getInstance();
 
        // 从工厂中获取 Bean Map 并将其放入 Bean 容器中
        Map<String, ?> beans = factory.getBeans();
        if (!CollectionUtils.isEmpty(beans)) {
            this.objects.putAll(beans);
        }
 
        return wsm;
    }
 
    protected FilterChainResolver createFilterChainResolver() {
        FilterChainResolver resolver = null;
        Ini ini = getIni();
        if (!CollectionUtils.isEmpty(ini)) {
            // Filter 可以从 [urls] 或 [filters] 片段中读取
            Ini.Section urls = ini.getSection(IniFilterChainResolverFactory.URLS);
            Ini.Section filters = ini.getSection(IniFilterChainResolverFactory.FILTERS);
            if (!CollectionUtils.isEmpty(urls) || !CollectionUtils.isEmpty(filters)) {
                // 通过工厂对象创建 FilterChainResolver 实例
                IniFilterChainResolverFactory factory = new IniFilterChainResolverFactory(ini, this.objects);
                resolver = factory.getInstance();
            }
        }
        return resolver;
    }
}

这上面一看init()方法就知道是找到shiro.ini文件,然后建立Ini对象,这里面有个**configure()**的方法,我们来看一下:

 protected void configure() {

        this.objects.clear();

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

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

看到这是不是出现惊喜了,创建了SecurityManager对象和FilterChainResolver
这将在web过滤器Filter中用到

猜你喜欢

转载自blog.csdn.net/weixin_42002747/article/details/103679509