對于Shiro(v1.2+)的SecurityManager的創(chuàng)建,在普通的應用程序中一般可以在main方法中這么創(chuàng)建
成都創(chuàng)新互聯(lián)主營洪洞網(wǎng)站建設的網(wǎng)絡公司,主營網(wǎng)站建設方案,重慶App定制開發(fā),洪洞h5微信小程序開發(fā)搭建,洪洞網(wǎng)站營銷推廣歡迎洪洞等地區(qū)企業(yè)咨詢
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
該方法讀取classpath路徑下的shiro.ini文件來構建SecurityManager,然而在web應用程序中,其是怎么創(chuàng)建的我們接下來逐步分析。
在web環(huán)境中我們會使用以下的Listener,而SecurityManager的創(chuàng)建就在Listener的初始化過程中【該Listener在shrio-web.jar中】
org.apache.shiro.web.env.EnvironmentLoaderListener
EnvironmentLoaderListener的繼承關系很簡單,如下所示
EnvironmentLoader的作用是負責在應用程序啟動的時候負責加載Shiro,同時將org.apache.shiro.web.mgt.WebSecurityManager設置到ServletContext中。
在初始化Shiro的過程中,在web.xml文件中配置的上下文參數(shù)“shiroEnvironmentClass”和“shiroConfigLocations”可以指導Shiro的初始化過程,當然,這兩個參數(shù)不是必須配置的,有默認值。
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);
}
//構造方法,默認未實現(xiàn)
customizeEnvironment(environment);
//調用environment的init方法初始化environment對象
LifecycleUtils.init(environment);
return environment;
}
protected WebEnvironment determineWebEnvironment(ServletContext servletContext) {
//從ServletContext的參數(shù)中獲取WebEnvironment的配置--shiroEnvironmentClass,如果有則創(chuàng)建實例返回
Class extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext);
WebEnvironment webEnvironment = null;
// 嘗試通過Java的ServiceLoader來查找WebEnvironment的實現(xiàn)類
if (webEnvironmentClass == null) {
webEnvironment = webEnvironmentFromServiceLoader();
}
// 如果上面的步驟都沒找到,則使用默認的WebEnvironment實現(xiàn)類IniWebEnvironment
if (webEnvironmentClass == null && webEnvironment == null) {
webEnvironmentClass = getDefaultWebEnvironmentClass();
}
// 創(chuàng)建WebEnvironment的實例
if (webEnvironmentClass != null) {
webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass);
}
return webEnvironment;
}
private WebEnvironment webEnvironmentFromServiceLoader() {
WebEnvironment webEnvironment = null;
/*
* 使用Java的ServiceLoader方式來查找WebEnvironment的實現(xiàn)類(查找jar包中META-INF下的services文件夾中的文件);
* 例如在某個services文件夾中有個名為org.apache.shiro.web.env.WebEnvironment的文件,然后在文件里面保存WebEnvironment的實現(xiàn)類全路徑;
* 可見,文件名為接口的全路徑,里面的內容為接口的實現(xiàn)類
* */
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的實現(xiàn)類經(jīng)歷了三次查找
1)從ServletContext的初始化參數(shù)
2)從jar包查找實現(xiàn)類
3)使用默認的IniWebEnvironment
在得到WebEnvironment的實現(xiàn)類并創(chuàng)建好實例后,接著便會調用其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創(chuàng)建WebSecurityManager實例
configure();
}
}
protected Ini parseConfig() {
//直接取,首次運行肯定為null
Ini ini = getIni();
//獲取配置文件路徑【該路徑信息就是web.xml文件中配置的,實例化該類的時候已經(jīng)在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.");
//從指定路徑下的配置文件中創(chuàng)建Ini實例
ini = getSpecifiedIni(configLocations);
}
if (CollectionUtils.isEmpty(ini)) {
log.debug("No INI instance or config locations specified. Trying default config locations.");
/*
* 如果沒有在web.xml中配置,則從默認的路徑下讀取配置文件并創(chuàng)建實例
* 1,/WEB-INF/shiro.ini
* 2,classpath:shiro.ini
* */
ini = getDefaultIni();
}
/*
* 為了保持向后兼容而提供getFrameworkIni方法來創(chuàng)建Ini對象并與上面得到的Ini對象合并.
* getFrameworkIni的默認實現(xiàn)返回null,經(jīng)過合并處理后返回的還是上面的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;
}
/**
* 解析配置文件創(chuàng)建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;
}
/**
* 加載制定路徑的配置文件,然后將文件流作為參數(shù)調用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();
//循環(huán)讀取每一行
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對象【靜態(tài)內部類】
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() {
//已經(jīng)創(chuàng)建好的Ini對象
Ini ini = getIni();
if (!CollectionUtils.isEmpty(ini)) {
factory.setIni(ini);
}
Map defaults = getDefaults();
if (!CollectionUtils.isEmpty(defaults)) {
factory.setDefaults(defaults);
}
//從WebIniSecurityManagerFactory實例中創(chuàng)建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方法的實現(xiàn)
由圖可見,在調用getInstance方法的時候,其實執(zhí)行的是位于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)實現(xiàn)創(chuàng)建實例的過程
* */
protected abstract T createInstance();
}
public abstract class IniFactorySupport extends AbstractFactory {
public T createInstance() {
/*
* 獲取Ini對象,前面已經(jīng)設置進來。
* 如果ini對象不存在,還會從默認的路徑來創(chuàng)建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),根據(jù)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();
}
//根據(jù)Ini來創(chuàng)建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構建對象【創(chuàng)建實例對象,加入到objects變量中,然后執(zhí)行各個對象的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實例創(chuàng)建完成,并設置到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下供后面使用。
系統(tǒng)啟動的時候執(zhí)行EnvironmentLoaderListener初始化方法并創(chuàng)建WebEnvironment實例,同時將實例對象保存到ServletContext中
1,創(chuàng)建WebEnvironment對象
1)讀取web.xml中的上下文參數(shù)shiroEnvironmentClass
2)通過ServiceLoader方式查找jar包中的配置
3)是用默認的IniWebEnvironment類型
2,調用WebEnvironment的init方法初始化WebEnvironment實例
注:WebEnvironment構造犯法里面會創(chuàng)建WebIniSecurityManagerFactory實例factory。
1)從指定或默認的路徑下解析shiro.ini文件生成Ini實例
2)將Ini實例設置給factory的ini屬性
3)將默認的IniFilterChainResolverFactory設置給factory的defaultBeans(Map)屬性
4)調用factory的getInstance方法創(chuàng)建SecurityManager對象
--解析Ini對象里面的信息,創(chuàng)建Realm等對象并設置給SecurityManager實例
5)將SecurityManager返回的objects(Map)添加到WebEnvironment的objects中。
默認的SecurityManager: DefaultWebSecurityManager
后面講接著介紹Session和Realm的使用