您的位置:首页 > 其它

ShiroFilterFactoryBean源码及阻截原理深入分析

2015-11-05 15:07 573 查看

本篇文章篇幅比较长,但是细看下去相信对学习Shiro应该会有帮助。好了,闲话不多说,直接进入正题:

Shiro提供了与Web集成的支持,其通过一个
ShiroFilter
入口来拦截需要安全控制的URL,然后进行相应的控制,ShiroFilter类似于如Strut2/SpringMVC这种web框架的前端控制器,其是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL是否需要登录/权限等工作。

而要在
Spring
中使用
Shiro
的话,可在
web.xml
中配置一个
DelegatingFilterProxy
DelegatingFilterProxy
作用是自动到
Spring
容器查找名字为
shiroFilter
filter-name
)的
bean
并把所有
Filter
的操作委托给它。

首先是在
web.xml
中配置
DelegatingFilterProxy

<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


配置好
DelegatingFilterProxy
后,下面只要再把
ShiroFilter
配置到
Spring
容器(此处为
Spring
的配置文件)即可:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
</bean>


可以看到我们使用了
ShiroFilterFactoryBean
来创建
shiroFilter
,这里用到了
Spring
中一种特殊的Bean——
FactoryBean
。当需要得到名为”shiroFilter“的bean时,会调用其
getObject()
来获取实例。下面我们通过分析
ShiroFilterFactoryBean
创建实例的过程来探究Shiro是如何实现安全拦截的:
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}


其中调用了
createInstance()
来创建实例:
protected AbstractShiroFilter createInstance() throws Exception {

// 这里是通过FactoryBean注入的SecurityManager(必须)
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}

if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}

FilterChainManager manager = createFilterChainManager();

PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);

return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}


可以看到创建
SpringShiroFilter
时用到了两个组件:
SecurityManager
ChainResolver


SecurityManager
:我们知道其在Shiro中的地位,类似于一个“安全大管家”,相当于SpringMVC中的
DispatcherServlet
或者Struts2中的
FilterDispatcher
,是Shiro的心脏,所有具体的交互都通过
SecurityManager
进行控制,它管理着所有
Subject
、且负责进行认证和授权、及会话、缓存的管理。

ChainResolver:Filter
链解析器,用来解析出该次请求需要执行的Filter链。

PathMatchingFilterChainResolver
ChainResolver
的实现类,其中还包含了两个重要组件
FilterChainManager
PatternMatcher


FilterChainManager
:管理着Filter和Filter链,配合
PathMatchingFilterChainResolver
解析出Filter链

PatternMatcher
:用来进行请求路径匹配,默认为Ant风格的路径匹配

先有一个大体的了解,那么对于源码分析会有不少帮助。下面会对以上两个重要的组件进行分析,包括
PathMatchingFilterChainResolver
FilterChainManager
。首先贴一段
ShiroFilter
的在配置文件中的定义:
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="unauthorizedUrl" value="/special/unauthorized" />
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter" />
<entry key="logout" value-ref="logoutFilter" />
<entry key="ssl" value-ref="sslFilter"></entry>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/resources/** = anon
/plugin/** = anon
/download/** = anon
/special/unauthorized = anon
/register = anon
/login = ssl,authc
/logout = logout
/admin/** = roles[admin]

/** = user
</value>
</property>
</bean>


再来看看
PathMatchingFilterChainResolver
FilterChainManager
的创建过程:
protected FilterChainManager createFilterChainManager() {

// 默认使用的FilterChainManager是DefaultFilterChainManager
DefaultFilterChainManager manager = new DefaultFilterChainManager();
// DefaultFilterChainManager默认会注册的filters(后面会列出)
Map<String, Filter> defaultFilters = manager.getFilters();

// 将ShiroFilterFactoryBean配置的一些公共属性(上面配置的loginUrl,successUrl,unauthorizeUrl)应用到默认注册的filter上去
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}

// 处理自定义的filter(上面配置的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();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
// 将Filter添加到manager中去,可以看到对于Filter的管理是依赖于FilterChainManager的
manager.addFilter(name, filter, false);
}
}

// 根据FilterChainDefinition的配置来构建Filter链(上面配置的filterChainDefinitions属性)
Map<String, String> chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();
String chainDefinition = entry.getValue();
// 后面会分析该步的源码,功能上就是创建Filter链
manager.createChain(url, chainDefinition);
}
}

return manager;
}


下面有必要来看看
DefaultFilterChainManager
的源码,分析一下上面调用到的方法。先来看看他的几个重要的属性:
private FilterConfig filterConfig;

private Map<String, Filter> filters; //pool of filters available for creating chains

private Map<String, NamedFilterList> filterChains; //key: chain name, value: chain


其中
filterConfig
仅在初始化Filter时有效,而我们自定义的Filter都不是init的,所以该属性可以暂时忽略()。

而后面两张map就重要了:filters中缓存了所有添加的
filter
,filterChains则缓存了所有的
filterChain
。其中前者的key是filter name,value是
Filter
。而后者的key是chain name,value是
NamedFilterList


有的童鞋可能会问
NamedFilterList
是怎么样的结构呢,你可以把它当成
List<Filter>
,这样就好理解了吧。下面再分析刚才
createFilterChainManager()
中调用过的
manager
的几个方法:

addFilter(缓存filter让manager来管理)

public void addFilter(String name, Filter filter, boolean init) {
addFilter(name, filter, init, true);
}

protected void addFilter(String name, Filter filter, boolean init, boolean overwrite) {
Filter existing = getFilter(name);
if (existing == null || overwrite) {
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
if (init) {
initFilter(filter);
}
this.filters.put(name, filter);
}
}


filter
缓存到
filters
这张
map
里,不管是默认注册的还是自定义的都需要
FilterChainManager
来统一管理。

createChain:创建filterChain并将定义的filter都加进去

// chainName就是拦截路径"/resources/**",chainDefinition就是多个过滤器名的字符串
public void createChain(String chainName, String chainDefinition) {
if (!StringUtils.hasText(chainName)) {
throw new NullPointerException("chainName cannot be null or empty.");
}
if (!StringUtils.hasText(chainDefinition)) {
throw new NullPointerException("chainDefinition cannot be null or empty.");
}

if (log.isDebugEnabled()) {
log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
}

// 先分离出配置的各个filter,比如
// "authc, roles[admin,user], perms[file:edit]" 分离后的结果是:
// { "authc", "roles[admin,user]", "perms[file:edit]" }
String[] filterTokens = splitChainDefinition(chainDefinition);

// 进一步分离出"[]"内的内容,其中nameConfigPair是一个长度为2的数组
// 比如 roles[admin,user] 经过解析后的nameConfigPair 为{"roles", "admin,user"}
for (String token : filterTokens) {
String[] nameConfigPair = toNameConfigPair(token);

// 得到了 拦截路径、filter以及可能的"[]"中的值,那么执行addToChain
addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}


addToChain

public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
if (!StringUtils.hasText(chainName)) {
throw new IllegalArgumentException("chainName cannot be null or empty.");
}
Filter filter = getFilter(filterName);
if (filter == null) {
throw new IllegalArgumentException("There is no filter with name '" + filterName + "' to apply to chain [" + chainName + "] in the pool of available Filters.  Ensure a " + "filter with that name/path has first been registered with the addFilter method(s).");
}

// 将"[]"中的匹配关系注册到filter中
applyChainConfig(chainName, filter, chainSpecificFilterConfig);

// 确保chain已经被加到filterChains这张map中了
NamedFilterList chain = ensureChain(chainName);
// 将该filter加入当前chain
chain.add(filter);
}


至此,
FilterChainManager
就创建完了,它无非就是缓存了两张
map
,没有什么逻辑上的操作。下面将
FilterChainManager
设置到
PathMatchingFilterChainResolver
中。
PathMatchingFilterChainResolver
实现了
FilterChainResolver
接口,该接口中只定义了一个方法:
FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);


通过解析请求来得到一个新的
FilterChain
。而
PathMatchingFilterChainResolver
实现了该接口,依靠了
FilterChainManager
中保存的
chainFilters
filters
这两张map来根据请求路径解析出相应的
filterChain
,并且和
originalChain
组合起来使用。下面具体看看
PathMatchingFilterChainResolver
中的实现:
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
// 得到 FilterChainManager
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}

String requestURI = getPathWithinApplication(request);

// chainNames就是刚定义的filterChains的keySet,也就是所有的路径集合(比如:["/resources/**","/login"])
for (String pathPattern : filterChainManager.getChainNames()) {

// 请求路径是否匹配某个 定义好的路径:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " + "Utilizing corresponding filter chain...");
}
// 找到第一个匹配的Filter链,那么就返回一个ProxiedFilterChain
return filterChainManager.proxy(originalChain, pathPattern);
}
}

return null;
}


这里返回只有两种情况,要么是
null
,要么就是一个
ProxiedFilterChain
。返回
null
并不表示中断
FilterChain
,而是只用
originChain
。而关于
ProxiedFilterChain
,它实现了
FilterChain
,内部维护了两份
FilterChain
(其实一个是
FilterChain
,另一个是
List<Filter>


FilterChain
也就是
web.xml
中注册的
Filter
形成的
FilterChain
,我们称之为
originChain
。而另一个
List<Filter>
则是我们在Shiro中注册的Filter链了,下面看看
ProxiedFilterChain
中关于
doFilter(...)
的实现:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.filters == null || this.filters.size() == this.index) {
//we've reached the end of the wrapped chain, so invoke the original one:
if (log.isTraceEnabled()) {
log.trace("Invoking original filter chain.");
}
this.orig.doFilter(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Invoking wrapped filter at index [" + this.index + "]");
}
this.filters.get(this.index++).doFilter(request, response, this);
}
}


可以看到,它会先执行Shiro中执行的
filter
,然后再执行
web.xml
中的
Filter
。不过要注意的是,需要等到
originChain
执行到
ShiroFilter
之后才会执行Shiro中的Filter链。

至此,两个组件的创建过程差不多都介绍完了,那么当这两个组件创建完毕后,是如何工作的呢?

先从
ShiroFilter
入手,因为它是总的拦截器,看看其中的
doFilterInternal(...)
方法:
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {

Throwable t = null;

try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

final Subject subject = createSubject(request, response);

//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
// 其实需要关心的就在这里
// touch一下session
updateSessionLastAccessTime(request, response);
// 执行Filter链
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
}

if (t != null) {
if (t instanceof ServletException) {
throw (ServletException) t;
}
if (t instanceof IOException) {
throw (IOException) t;
}
//otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
String msg = "Filtered request failed.";
throw new ServletException(msg, t);
}
}


跟进
executeChain(...)
方法:
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}


如何得到
FilterChain
的呢?如果你认真的看到这里,那么你应该不难想到其中肯定利用了刚才注册的
ChainResolver

protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;

FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
return origChain;
}

FilterChain resolved = resolver.getChain(request, response, origChain);
if (resolved != null) {
log.trace("Resolved a configured FilterChain for the current request.");
chain = resolved;
} else {
log.trace("No FilterChain configured for the current request.  Using the default.");
}

return chain;
}


猜对了~并且也验证了当
resolver.getChain(...)
返回
null
时,直接使用
originChain
了。然后执行返回的
FilterChain
doFilter(...)
方法。这个过程我们再脱离代码来分析一下:当我们从浏览器发出一个请求,究竟发生了什么?

这里只站在
Filter
的层面来分析。服务器启动后,读取
web.xml
中的
filter
filter-mapping
节点后组成
FilterChain
,对请求进行拦截。拦截的顺序按照filter节点的定义顺序,Shiro利用
ShiroFilter
来充当一个总的拦截器来分发所有需要被Shiro拦截的请求,所以我们看到在Shiro中我们还可以自定义拦截器。
ShiroFilter
根据它在拦截器中的位置,只要执行到了那么就会暂时中断原
FilterChain
的执行,先执行Shiro中定义的
Filter
,最后再执行原
FilterChian
。可以打个比方,比如说本来有一条铁链,一直蚂蚁从铁链的开端往末端爬,其中某一环叫
ShiroFilter
,那么当蚂蚁爬到
ShiroFilter
这一环时,将铁链打断,并且接上另一端铁链(Shiro中自定义的
Filter
),这样就构成了一条新的铁链。然后蚂蚁继续爬行(后续的执行过程)。

最后附上默认注册的filters:
public enum DefaultFilter {

anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
}


水平有限,写得蛮不容易,看源码加写花了整整2天。希望对大家能有帮助~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: