对于Shiro(v1.2+)的SecurityManager的创建,在普通的应用程序中一般可以在main方法中这么创建
成都创新互联主营洪洞网站建设的网络公司,主营网站建设方案,重庆App定制开发,洪洞h5微信小程序开发搭建,洪洞网站营销推广欢迎洪洞等地区企业咨询
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
该方法读取classpath路径下的shiro.ini文件来构建SecurityManager,然而在web应用程序中,其是怎么创建的我们接下来逐步分析。
在web环境中我们会使用以下的Listener,而SecurityManager的创建就在Listener的初始化过程中【该Listener在shrio-web.jar中】
org.apache.shiro.web.env.EnvironmentLoaderListener
EnvironmentLoaderListener的继承关系很简单,如下所示
EnvironmentLoader的作用是负责在应用程序启动的时候负责加载Shiro,同时将org.apache.shiro.web.mgt.WebSecurityManager设置到ServletContext中。
在初始化Shiro的过程中,在web.xml文件中配置的上下文参数“shiroEnvironmentClass”和“shiroConfigLocations”可以指导Shiro的初始化过程,当然,这两个参数不是必须配置的,有默认值。
shiroEnvironmentClass:制定继承自WebEnvironment的自定义类,默认对象为IniWebEnvironment。
shiroConfigLocations:制定shiro初始化时用的配置文件路径,默认会先查询/WEB-INF/shiro.ini,如果没找到再查找classpath:shiro.ini。
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 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 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;
//保存当前的ServletContext对象
environment.setServletContext(sc);
//如果在web.xml设置了配置文件的路径,则在此设置到environment中
if (configSpecified && (environment instanceof ResourceConfigurable)) {
((ResourceConfigurable) environment).setConfigLocations(configLocations);
}
//构造方法,默认未实现
customizeEnvironment(environment);
//调用environment的init方法初始化environment对象
LifecycleUtils.init(environment);
return environment;
}
protected WebEnvironment determineWebEnvironment(ServletContext servletContext) {
//从ServletContext的参数中获取WebEnvironment的配置--shiroEnvironmentClass,如果有则创建实例返回
Class extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext);
WebEnvironment webEnvironment = null;
// 尝试通过Java的ServiceLoader来查找WebEnvironment的实现类
if (webEnvironmentClass == null) {
webEnvironment = webEnvironmentFromServiceLoader();
}
// 如果上面的步骤都没找到,则使用默认的WebEnvironment实现类IniWebEnvironment
if (webEnvironmentClass == null && webEnvironment == null) {
webEnvironmentClass = getDefaultWebEnvironmentClass();
}
// 创建WebEnvironment的实例
if (webEnvironmentClass != null) {
webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass);
}
return webEnvironment;
}
private WebEnvironment webEnvironmentFromServiceLoader() {
WebEnvironment webEnvironment = null;
/*
* 使用Java的ServiceLoader方式来查找WebEnvironment的实现类(查找jar包中META-INF下的services文件夹中的文件);
* 例如在某个services文件夹中有个名为org.apache.shiro.web.env.WebEnvironment的文件,然后在文件里面保存WebEnvironment的实现类全路径;
* 可见,文件名为接口的全路径,里面的内容为接口的实现类
* */
ServiceLoader serviceLoader = ServiceLoader.load(WebEnvironment.class);
Iterator iterator = serviceLoader.iterator();
// 如果找到则使用第一个
if (iterator.hasNext()) {
webEnvironment = iterator.next();
}
// 如果不止找到一个,则抛出异常
if (iterator.hasNext()) {
List allWebEnvironments = new ArrayList();
allWebEnvironments.add(webEnvironment.getClass().getName());
while (iterator.hasNext()) {
allWebEnvironments.add(iterator.next().getClass().getName());
}
throw new ConfigurationException("ServiceLoader for class [" + WebEnvironment.class + "] returned more then one " +
"result. ServiceLoader must return zero or exactly one result for this class. Select one using the " +
"servlet init parameter '"+ ENVIRONMENT_CLASS_PARAM +"'. Found: " + allWebEnvironments);
}
return webEnvironment;
}
}
综上得知,查找WebEnvironment的实现类经历了三次查找
1)从ServletContext的初始化参数
2)从jar包查找实现类
3)使用默认的IniWebEnvironment
在得到WebEnvironment的实现类并创建好实例后,接着便会调用其init方法,这里假设得到的是默认的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() {
//实例化WebIniSecurityManagerFactory对象
factory = new WebIniSecurityManagerFactory();
}
/**
* 初始化本实例
*/
public void init() {
//解析shiiro.ini配置文件并生成对应的Ini实例
setIni(parseConfig());
//使用Ini信息,通过WebIniSecurityManagerFactory创建WebSecurityManager实例
configure();
}
}
protected Ini parseConfig() {
//直接取,首次运行肯定为null
Ini ini = getIni();
//获取配置文件路径【该路径信息就是web.xml文件中配置的,实例化该类的时候已经在EnvironmentLoader中设置】
String[] configLocations = getConfigLocations();
if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) &&
configLocations != null && configLocations.length > 0) {
//如果Ini对象不为空,并且configLocations也不为空,给出提示信息
log.warn("Explicit INI instance has been provided, but configuration locations have also been " +
"specified. The {} implementation does not currently support multiple Ini config, but this may " +
"be supported in the future. Only the INI instance will be used for configuration.",
IniWebEnvironment.class.getName());
}
if (CollectionUtils.isEmpty(ini)) {
log.debug("Checking any specified config locations.");
//从指定路径下的配置文件中创建Ini实例
ini = getSpecifiedIni(configLocations);
}
if (CollectionUtils.isEmpty(ini)) {
log.debug("No INI instance or config locations specified. Trying default config locations.");
/*
* 如果没有在web.xml中配置,则从默认的路径下读取配置文件并创建实例
* 1,/WEB-INF/shiro.ini
* 2,classpath:shiro.ini
* */
ini = getDefaultIni();
}
/*
* 为了保持向后兼容而提供getFrameworkIni方法来创建Ini对象并与上面得到的Ini对象合并.
* getFrameworkIni的默认实现返回null,经过合并处理后返回的还是上面的Ini对象
* */
ini = mergeIni(getFrameworkIni(), ini);
if (CollectionUtils.isEmpty(ini)) {
String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured.";
throw new ConfigurationException(msg);
}
return ini;
}
/**
* 解析配置文件创建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)) {
String msg = "Required configuration location '" + configLocation + "' does not exist or did not " +
"contain any INI configuration.";
throw new ConfigurationException(msg);
}
return ini;
}
/**
* 加载制定路径的配置文件,然后将文件流作为参数调用Ini实例对象的load方法来初始化Ini对象
* */
private Ini convertPathToIni(String path, boolean required) {
Ini ini = null;
if (StringUtils.hasText(path)) {
InputStream is = null;
//SHIRO-178: Check for servlet context resource and not only resource paths:
if (!ResourceUtils.hasResourcePrefix(path)) {
is = getServletContextResourceStream(path);
} else {
try {
is = ResourceUtils.getInputStreamForPath(path);
} catch (IOException e) {
if (required) {
throw new ConfigurationException(e);
} else {
if (log.isDebugEnabled()) {
log.debug("Unable to load optional path '" + path + "'.", e);
}
}
}
}
if (is != null) {
ini = new Ini();
ini.load(is);
} else {
if (required) {
throw new ConfigurationException("Unable to load resource path '" + path + "'");
}
}
}
return ini;
}
再看看Ini对象的初始化过程
public class Ini implements Map {
private static transient final Logger log = LoggerFactory.getLogger(Ini.class);
public static final String DEFAULT_SECTION_NAME = ""; //empty string means the first unnamed section
public static final String DEFAULT_CHARSET_NAME = "UTF-8";
public static final String COMMENT_POUND = "#";
public static final String COMMENT_SEMICOLON = ";";
public static final String SECTION_PREFIX = "[";
public static final String SECTION_SUFFIX = "]";
protected static final char ESCAPE_TOKEN = '\\';
private final Map sections;
/**
* Creates a new empty {@code Ini} instance.
*/
public Ini() {
this.sections = new LinkedHashMap();
}
public void load(InputStream is) throws ConfigurationException {
if (is == null) {
throw new NullPointerException("InputStream argument cannot be null.");
}
InputStreamReader isr;
try {
isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
throw new ConfigurationException(e);
}
load(isr);
}
public void load(Reader reader) {
Scanner scanner = new Scanner(reader);
try {
load(scanner);
} finally {
try {
scanner.close();
} catch (Exception e) {
log.debug("Unable to cleanly close the InputStream scanner. Non-critical - ignoring.", e);
}
}
}
public void load(Scanner scanner) {
String sectionName = DEFAULT_SECTION_NAME;
StringBuilder sectionContent = new StringBuilder();
//循环读取每一行
while (scanner.hasNextLine()) {
String rawLine = scanner.nextLine();
//取出两边的空格
String line = StringUtils.clean(rawLine);
if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
//忽略空行和注释
continue;
}
//获取section名称,格式为 [main] 这种,此时返回“main”
String newSectionName = getSectionName(line);
if (newSectionName != null) {
//前面section的配置信息收集完成,添加section配置
addSection(sectionName, sectionContent);
//为本次的section重置StringBuilder对象,用户存放该section的配置信息
sectionContent = new StringBuilder();
sectionName = newSectionName;
if (log.isDebugEnabled()) {
log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
}
} else {
//添加配置信息
sectionContent.append(rawLine).append("\n");
}
}
//添加Section的配置信息
addSection(sectionName, sectionContent);
}
private void addSection(String name, StringBuilder content) {
if (content.length() > 0) {
String contentString = content.toString();
String cleaned = StringUtils.clean(contentString);
if (cleaned != null) {
//构建Section对象【静态内部类】
Section section = new Section(name, contentString);
if (!section.isEmpty()) {
//以键值对的方式保存Section对象
sections.put(name, section);
}
}
}
}
public static class Section implements Map {
private final String name;
private final Map props;
/*
* 解析收集的配置信息,将配置信息保存到props对象中
* */
private Section(String name, String sectionContent) {
if (name == null) {
throw new NullPointerException("name");
}
this.name = name;
Map props;
if (StringUtils.hasText(sectionContent) ) {
props = toMapProps(sectionContent);
} else {
props = new LinkedHashMap();
}
if ( props != null ) {
this.props = props;
} else {
this.props = new LinkedHashMap();
}
}
}
}
到此,在IniWebEnvironment实例中通过解析配置文件得到了Ini对象,该对象里面保存了配置文件中的每个Section信息,那么接着就要使用该Ini对象来构建WebSecurityManager了,也就是调用IniWebEnvironment 的configure方法
public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
protected void configure() {
//Map对象
this.objects.clear();
WebSecurityManager securityManager = createWebSecurityManager();
setWebSecurityManager(securityManager);
FilterChainResolver resolver = createFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
protected Map getDefaults() {
Map defaults = new HashMap();
defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory());
return defaults;
}
protected WebSecurityManager createWebSecurityManager() {
//已经创建好的Ini对象
Ini ini = getIni();
if (!CollectionUtils.isEmpty(ini)) {
factory.setIni(ini);
}
Map defaults = getDefaults();
if (!CollectionUtils.isEmpty(defaults)) {
factory.setDefaults(defaults);
}
//从WebIniSecurityManagerFactory实例中创建WebSecurityManager
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;
}
}
接着看看WebIniSecurityManagerFactory的getInstance方法的实现
由图可见,在调用getInstance方法的时候,其实执行的是位于AbstractFactory中的getInstance方法
public abstract class AbstractFactory implements Factory {
public T getInstance() {
T instance;
if (isSingleton()) {
if (this.singletonInstance == null) {
this.singletonInstance = createInstance();
}
instance = this.singletonInstance;
} else {
instance = createInstance();
}
if (instance == null) {
String msg = "Factory 'createInstance' implementation returned a null object.";
throw new IllegalStateException(msg);
}
return instance;
}
/*
* 子类(IniFactorySupport)实现创建实例的过程
* */
protected abstract T createInstance();
}
public abstract class IniFactorySupport extends AbstractFactory {
public T createInstance() {
/*
* 获取Ini对象,前面已经设置进来。
* 如果ini对象不存在,还会从默认的路径来创建Ini对象
* */
Ini ini = resolveIni();
T instance;
if (CollectionUtils.isEmpty(ini)) {
//如果Ini对象不存在,则调动子类(IniSecurityManagerFactory)使用默认的SecurityManager实例对象
log.debug("No populated Ini available. Creating a default instance.");
instance = createDefaultInstance();
if (instance == null) {
String msg = getClass().getName() + " implementation did not return a default instance in " +
"the event of a null/empty Ini configuration. This is required to support the " +
"Factory interface. Please check your implementation.";
throw new IllegalStateException(msg);
}
} else {
log.debug("Creating instance from Ini [" + ini + "]");
//调用子类(IniSecurityManagerFactory),根据Ini对象的信息来构建SecurityManager对象
instance = createInstance(ini);
if (instance == null) {
String msg = getClass().getName() + " implementation did not return a constructed instance from " +
"the createInstance(Ini) method implementation.";
throw new IllegalStateException(msg);
}
}
return instance;
}
protected abstract T createInstance(Ini ini);
protected abstract T createDefaultInstance();
}
public class IniSecurityManagerFactory extends IniFactorySupport {
public static final String MAIN_SECTION_NAME = "main";
public static final String SECURITY_MANAGER_NAME = "securityManager";
public static final String INI_REALM_NAME = "iniRealm";
private ReflectionBuilder builder;
public IniSecurityManagerFactory() {
this.builder = new ReflectionBuilder();
}
//默认的SecurityManager对象【其实被WebIniSecurityManagerFactory复写,返回的是DefaultWebSecurityManager】
protected SecurityManager createDefaultInstance() {
return new DefaultSecurityManager();
}
//根据Ini来创建SecurityManager对象
protected SecurityManager createInstance(Ini ini) {
if (CollectionUtils.isEmpty(ini)) {
throw new NullPointerException("Ini argument cannot be null or empty.");
}
SecurityManager securityManager = createSecurityManager(ini);
if (securityManager == null) {
String msg = SecurityManager.class + " instance cannot be null.";
throw new ConfigurationException(msg);
}
return securityManager;
}
private SecurityManager createSecurityManager(Ini ini) {
return createSecurityManager(ini, getConfigSection(ini));
}
//获取[main]的配置,如果没得到则获取默认的配置
private Ini.Section getConfigSection(Ini ini) {
Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME);
if (CollectionUtils.isEmpty(mainSection)) {
//try the default:
mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME);
}
return mainSection;
}
@SuppressWarnings({"unchecked"})
private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
/*
* 注意,createDefaults被子类WebIniSecurityManagerFactory复写,
* 但其实也会首先调用本类的createDefaults方法,只是在结果中再添加了些默认的Filter实例。
*
* 然后将结果保存在ReflectionBuilder对象的objects【Map】属性中,此时里面包含了默认的SecurityManager、Realm以及各种默认Filter实例;
*
* 最后将createDefaults返回的Map全部加到ReflectionBuilder对象的objects【Map】中取缓存
* */
getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
//使用ReflectionBuilder构建对象【创建实例对象,加入到objects变量中,然后执行各个对象的init方法,同时返回objects对象】
Map objects = buildInstances(mainSection);
//直接从ReflectionBuilder对象中取出SecurityManager类型的对象
SecurityManager securityManager = getSecurityManagerBean();
/*
* 如果securityManager不为RealmSecurityManager类型则返回true;
* 如果是RealmSecurityManager类型,但是里面没有Realm实例,返回为true;
* 否则返回false
* */
boolean autoApplyRealms = isAutoApplyRealms(securityManager);
if (autoApplyRealms) {
//筛选其中的Realms对象【Realm或RealmFactory类型】
Collection realms = getRealms(objects);
if (!CollectionUtils.isEmpty(realms)) {
//如果securityManager不是RealmSecurityManager类型则抛出异常,否则给securityManager设置Realms
applyRealmsToSecurityManager(realms, securityManager);
}
}
return securityManager;
}
}
到此,SecurityManager实例创建完成,并设置到IniWebEnvironment的属性objects[Map]中
public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable {
protected void configure() {
//Map对象
this.objects.clear();
WebSecurityManager securityManager = createWebSecurityManager();
setWebSecurityManager(securityManager);
//获取shiro.ini文件中配置的'filters' 或 'urls'项的Filter,加入objects对象中
FilterChainResolver resolver = createFilterChainResolver();
if (resolver != null) {
setFilterChainResolver(resolver);
}
}
}
到此,Shiro的初始化过程完成,在EnvironmentLoaderListener 中将会把该IniWebEnvironment对象保存在ServletContext下供后面使用。
系统启动的时候执行EnvironmentLoaderListener初始化方法并创建WebEnvironment实例,同时将实例对象保存到ServletContext中
1,创建WebEnvironment对象
1)读取web.xml中的上下文参数shiroEnvironmentClass
2)通过ServiceLoader方式查找jar包中的配置
3)是用默认的IniWebEnvironment类型
2,调用WebEnvironment的init方法初始化WebEnvironment实例
注:WebEnvironment构造犯法里面会创建WebIniSecurityManagerFactory实例factory。
1)从指定或默认的路径下解析shiro.ini文件生成Ini实例
2)将Ini实例设置给factory的ini属性
3)将默认的IniFilterChainResolverFactory设置给factory的defaultBeans(Map)属性
4)调用factory的getInstance方法创建SecurityManager对象
--解析Ini对象里面的信息,创建Realm等对象并设置给SecurityManager实例
5)将SecurityManager返回的objects(Map)添加到WebEnvironment的objects中。
默认的SecurityManager: DefaultWebSecurityManager
后面讲接着介绍Session和Realm的使用