要想在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这个类的架构:
-
EventListener 是一个标志接口,里面没有任何的方法,Servlet 容器中所有的 Listener 都要继承这个接口(这是 Servlet 规范)。
-
ServletContextListener 是一个 ServletContext 的监听器,用于监听容器的启动与关闭事件,包括如下两个方法:
- void contextInitialized(ServletContextEvent sce); // 当容器启动时调用
- void contextDestroyed(ServletContextEvent sce); // 当容器关闭时调用
可以从 ServletContextEvent 中直接获取 ServletContext 对象。
-
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 就是为了:
-
当容器启动时,读取 web.xml 文件,从中获取 WebEnvironment 接口的实现类(默认是 IniWebEnvironment),初始化该实例,并将其加载到 ServletContext 中。
扫描二维码关注公众号,回复: 11347357 查看本文章 -
当容器关闭时,销毁 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中用到