閱讀源碼有助於陶冶情操,本文旨在簡單的分析shiro在Spring中的使用
介紹
Shiro是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理主要功能;另外其也提供了Web Support、緩存、Remember Me、並發等功能。
而Shiro的架構核心可看來自Java技術棧提供的圖片
對上述的圖作簡單的描述
- Subject 用戶視圖,shiro對用戶或者第三方服務、任何需要登錄的東西統一稱之為Subject對象
- SecurityManager 安全管理器,其中內含緩存管理、會話管理,主要是對登錄過來的Subject視圖作一系列的安全操作(包括下述提及的Realms的關聯使用)
- Realms 數據管理器,其主要充當shiro與安全數據交互的橋梁,幫助shiro容器完成用戶的校驗以及認證功能。可配置多個,但必須配置至少一個。shiro也提供了默認的實現比如ladp/jdbc等方式的用戶校驗認證
更多的部分讀者可參閱知乎Java技術棧專欄文章非常詳盡的 Shiro 架構解析。本文只分析其與Spring如何搭配使用
web.xml配置Shiro環境
配置清單如下
<!-- shiro 安全過濾器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<!--spring調用shiro代理-->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<!--是否開啟Filter的生命周期,主要涉及init和destory-->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
DelegatingFilterProxy#initFilterBean
入口方法,首先會執行初始化工作,shiro和spring security的代理加載實體Filter類都是通過此入口方法,代碼清單如下
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.如果沒有指定則采用對應的<filter-name>的值
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
//初始化
this.delegate = initDelegate(wac);
}
}
}
}
我們主要關心DelegatingFilterProxy#initDelegate
初始化委托Filter方法,代碼清單如下
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
//根據名字獲取ApplicationContext環境的bean,且必須是Filter的實現類
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
//true則初始化對應的Filter類
if (isTargetFilterLifecycle()) {
//這里一般對應的Filter類為`org.apache.shiro.spring.web.ShiroFilterFactoryBean`
delegate.init(getFilterConfig());
}
return delegate;
}
由以上代碼可以確認,application-shiro.xml
Spring配置文件必須含有與web.xml
中DelegatingFilterProxy
類對應的<filter-name>
的bean配置
示例文件
<!-- Shiro的Web過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--securityManager屬性必須存在,通過安全管理器調用Realm接口實現的認證/授權方法-->
<property name="securityManager" ref="securityManager"/>
<!--登錄、主頁、未通過授權頁面的url-->
<property name="loginUrl" value="/login"/>
<property name="successUrl" value="/index.html"/>
<property name="unauthorizedUrl" value="/403.html"/>
<!--自定義的filter集合-->
<property name="filters">
<!--這里采用map集合主要是響應內部屬性Map<String, Filter> filters-->
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="perm" value-ref="permissionsAuthorizationFilter"/>
<entry key="sysUser" value-ref="sysUserFilter"/>
<entry key="user" value-ref="userFilter"/>
</util:map>
</property>
<!--對應路徑請求Filter鏈,=右邊代表是filter過濾引用,且是順序執行,url且從上置下優先匹配,一旦匹配則不往下搜尋-->
<!--=右邊表達也可為authc[role1,role2]表明訪問該url必須通過認證且具有role1、role2的角色權限-->
<property name="filterChainDefinitions">
<value>
/test/** = anon
/login = captcha,authc
/index = anon
/403.html = anon
/login.html = anon
/favicon.ico = anon
/static/** = anon
/index.html=user,sysUser
/welcome.html=user,sysUser
/** = user,sysUser,perm
</value>
</property>
</bean>
ShiroFilterFactoryBean#createInstance方法返回Filter實例
通過ShiroFilterFactoryBean#createInstance方法
創建對應的Filter實例,我們看下創建的實例是何種人物,代碼清單如下
//創建實例,實例對象為SpringShiroFilter
protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
//<property name="securityManager">屬性必須存在
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
//securityManager的實現類必須是WebSecurityManager的實現類,表明Spring的shiro結合是web方面的
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
//filter過濾鏈管理類創建
FilterChainManager manager = createFilterChainManager();
//Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
其中涉及filter過濾鏈管理類的創建,簡單分析下,代碼清單如下
protected FilterChainManager createFilterChainManager() {
//采用默認filter過濾鏈管理類
DefaultFilterChainManager manager = new DefaultFilterChainManager();
//獲取默認Filter類集合
Map<String, Filter> defaultFilters = manager.getFilters();
//apply global settings if necessary:
for (Filter filter : defaultFilters.values()) {
//主要設置loginUrl、successUrl、unauthorizedUrl
//即權限Filter類設置loginUrl;認證Filter類設置successUrl、loginUrl;授權Filter類設置unauthorizedUrl,loginUrl
applyGlobalPropertiesIfNecessary(filter);
}
//Apply the acquired and/or configured filters:用戶自定義的Filter類,通過<property name="filters">設定的
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
//同樣設置url屬性
applyGlobalPropertiesIfNecessary(filter);
//設置名字
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
//'init' argument is false, since Spring-configured filters should be initialized
//in Spring (i.e. 'init-method=blah') or implement InitializingBean:
manager.addFilter(name, filter, false);
}
}
//build up the chains: url與對應的filter過濾鏈配置,解析的是<property name="filterChainDefinitions">屬性
//url可對應多個filter,且保存filter是通過ArrayList來保存的,表明過濾鏈則由寫的先后順序執行
Map<String, String> chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
//chainDefinition可能為authc[role1,role2]或者authc,anno等
String chainDefinition = entry.getValue();
//解析以上表達式並保存至相應的自定義filter對象中
manager.createChain(url, chainDefinition);
}
}
return manager;
}
總結
Spring容器中使用Apache Shiro,需要配置
- web.xml中配置
節點,節點類為org.springframework.web.filter.DelegatingFilterProxy - spring配置shiro文件中需要
節點,節點id名必須與web.xml定義中的 節點的 屬性一致,且beanClass為 org.apache.shiro.spring.web.ShiroFilterFactoryBean
Spring配置文件中配置
org.apache.shiro.spring.web.ShiroFilterFactoryBean
主要設置shiro的相關filter用於攔截請求,其中的屬性含義見本文的示例文件說明
下節預告
Spring-shiro源碼陶冶-DefaultFilter