前言
shiro 最常见的使用场景就是在 spring web 里面,本篇文章会讲解 shiro 如何集成 spring web 的原理。shiro 会自定义 Filter 嵌入到 servlet 中,当每次请求到来时,会先经过 shiro filter。shiro filter 会解析请求提取出 cookie,然后根据 cookie 完成对此请求的用户身份确认和权限检查,下面会详细介绍整个原理。
Servlet Filter 原理
我们知道 spring mvc 是基于 servlet 开发的,在 servlet 中有一个比较重要的概念 Filter
。shiro 集成 spring web 就是依靠Filter
实现的,下面是集成 shiro 的运行流程图
sequenceDiagram
participant Tomcat
participant ShiroFilter
participant ServletFilter
participant Spring Servlet
Tomcat->>ShiroFilter:
ShiroFilter->>ServletFilter:
ServletFilter->>Spring Servlet:
Spring Servlet->>Tomcat: 返回响应
首先 tomcat 接收到请求,然后经过 ShiroFilter 验证用户身份和权限。再转发给其他ServletFilter
处理,最后才会交给 Spring 处理。
Shiro Filter 类图
Shiro 的 Filter类比较复杂,可以将其分为两类,一种是下图左边的主 Filter,它会被嵌入到Servlet
里。而右边的 Filter 会只负责一个单独的功能,称作为独立 Filter,被主 Filter 负责管理。
classDiagram
class Filter
%% <<interface>> Filter
class ServletContextSupport
class AbstractFilter
%% <<abstract>> AbstractFilter
Filter <|-- AbstractFilter
ServletContextSupport <|-- AbstractFilter
class NameableFilter
%% <<abstract>> NameableFilter
AbstractFilter <|-- NameableFilter
class OncePerRequestFilter
%% <<abstract>> OncePerRequestFilter
NameableFilter <|-- OncePerRequestFilter
class AbstractShiroFilter
%% <<abstract>> AbstractShiroFilter
OncePerRequestFilter <|-- AbstractShiroFilter
class SpringShiroFilter
AbstractShiroFilter <|-- SpringShiroFilter
class AdviceFilter
%% <<abstract>> AdviceFilter
OncePerRequestFilter <|-- AdviceFilter
class PathMatchingFilter
%% <<abstract>> PathMatchingFilter
AdviceFilter <|-- PathMatchingFilter
class AnonymousFilter
PathMatchingFilter <|-- AnonymousFilter
class 其他Filter
PathMatchingFilter <|-- 其他Filter
Filter
接口是 Servlet 中定义的,如果要添加自定义Filter
必须要实现它。
AbstractFilter
保存了Servlet
的相关配置。
NameableFilter
保存了该Filter
的名称。
OncePerRequestFilter
保证了在每次处理 http 请求时,保证该 filter 只会执行一次。
AbstractShiroFilter
是最核心的 filter,也比较复杂,下面会着重介绍。
SpringShiroFilter
只是简单的继承了AbstractShiroFilter
。
右边的独立 Filter 相关类在后面会有介绍。
主 Filter
shrio 只会创建一个主 Filter,嵌入到Servlet
中。然后其它的独立 Filter 会被添加到主 Filter 里,这种设计可以简化对Servlet
的修改,并且可以自定义管理多个独立 Filter。主 Filter 会以 Chain 的方式来组织管理,如下图所示。主 Filter 根据请求的路径不同,选择不同的 FilterChain 来处理。
AbstractShiroFilter
作为实现主 Filter 的核心类,需要详细讲解。它在处理请求时,会先创建Subject
实例,然后开辟新线程来处理。
|
|
在创建线程的时候,shiro 做了一些初始化,它会绑定当前的Subject
和SecurityManager
,存储到线程上下文。结合前期这篇文章,就可以看到线程安全的使用。
继续看看executeChain
的代码,可以看到 shiro 会有一个根据请求选择FilterChain
的过程。
|
|
FilterChain 匹配原理
shiro 支持根据请求来选择对应的FilterChain
,这个功能由FilterChainResolver
接口定义。
|
|
它有两个子类,PathMatchingFilterChainResolver
和SimpleFilterChainResolver
,都是根据请求路径来判断的。两者的实现没什么本质区别,都是使用Map
保存了匹配规则和对应的FilterChain
,只不过前者将路径匹配和FilterChain
管理分离开了。
SimpleFilterChainResolver 类
首先来讲讲SimpleFilterChainResolver
的原理,它非常简单
|
|
SimpleFilterChain
包含了两部分的Filter
,原始的 Serlvet FilterChain
,和上述Filter
列表。它被调用时,会先执行Filter
列表,然后再执行Serlvet FilterChain
。
|
|
PathMatchingFilterChainResolver类
PathMatchingFilterChainResolver
是 shiro 默认的实现类,它只负责匹配的功能,管理FilterChain
的功能被分离开来了,使得我们可以自由定义FilterChain
。
管理FilterChain
的功能由FilterChainManager
负责,目前只有DefaultFilterChainManager
一种实现。它的实现也非常简单,我们通过设置filters
和filterChains
的值,就可以完成配置。
|
|
独立 Filter
我们再来看看独立Filter 的类图,依次从上到下看。
AdviceFilter
增加了preHandle
方法,子类可以通过实现它,来控制是否继续执行 FilterChain
。
PathMatchingFilter
增加了请求路径匹配的功能。
AccessControlFilter
作为身份和权限认证的父类,提供了执行失败后的回调函数。
classDiagram
class OncePerRequestFilter
<<abstract>> OncePerRequestFilter
class AdviceFilter
<<abstract>> AdviceFilter
OncePerRequestFilter <|-- AdviceFilter
class PathMatchingFilter
<<abstract>> PathMatchingFilter
AdviceFilter <|-- PathMatchingFilter
class AnonymousFilter
PathMatchingFilter <|-- AnonymousFilter
class AccessControlFilter
<<abstract>> AccessControlFilter
PathMatchingFilter <|-- AccessControlFilter
class AuthenticationFilter
<<abstract>> AuthenticationFilter
AccessControlFilter <|-- AuthenticationFilter
class AuthorizationFilter
<<abstract>> AuthorizationFilter
AccessControlFilter <|-- AuthorizationFilter
下面的类可以分为三块,
AnonymousFilter
,表示允许匿名访问AuthenticationFilter
及其子类,表示身份认证方面的AuthorizationFilter
及其子类,表示权限检查方面的
匿名访问 Filter
AnonymousFilter
允许匿名访问,它复写了onPreHandle
方法,总是放回 true,表示运行通过。
|
|
AccessControlFilter 类
AccessControlFilter
实现了onPreHandle
方法,并且还定义了isAccessAllowed
和onAccessDenied
的方法。子类需要实现isAccessAllowed
来负责用户身份认证或者权限检查的逻辑。还需要实现onAccessDenied
方法来执行失败后的处理逻辑。
AccessControlFilter
会通过两个方法处理的接口,来决定是否要继续执行 filter chain。
身份认证 Filter
身份认证的 Filter 类也比较复杂,从上到下依次介绍:
AuthenticationFilter
实现了isAccessAllowed
方法,它会判断当前的Subject
是否对已经认证过了。
AuthenticatingFilter
提供了登录的方法。
FormAuthenticationFilter
提供了表单登录的方法。当用户请求登录地址时(默认为login.jsp
),它会自动完成登录操作,并且实现跳转页面操作。我们不用再去定义处理登录的 controller。
BasicHttpAuthenticationFilter
提供了 http basic 验证方法。
classDiagram
class AuthenticatingFilter
<<abstract>> AuthenticatingFilter
AuthenticationFilter <|-- AuthenticatingFilter
class FormAuthenticationFilter
AuthenticatingFilter <|-- FormAuthenticationFilter
class HttpAuthenticationFilter
<<abstract>> HttpAuthenticationFilter
AuthenticatingFilter <|-- HttpAuthenticationFilter
class BasicHttpAuthenticationFilter
HttpAuthenticationFilter <|-- BasicHttpAuthenticationFilter
我们也可以自定义 Filter,只需要继承AuthenticationFilter
类,然后实现onAccessDenied
方法即可。比如我们在实现 restful 风格的 api 时,当发现用户没登录时,只需要返回 json。
|
|
权限检查 Filter
权限认证的 Filter 类会相对简单一些,首先看看父类AuthorizationFilter
。它实现了onAccessDenied
方法,当权限检查没通过时,会被调用。如果用户没有认证通过的话,AuthorizationFilter
会返回 302 响应,跳转到登录地址。如果是权限校检没通过,那么会返回 401 响应。
AuthorizationFilter
还有很多子类,每个子类都会实现各自的权限校检逻辑。下面表列举了各种子类
类名 | 检查逻辑 |
---|---|
HostFilter | 根据用户的ip来判断,是否允许通过 |
PermissionsAuthorizationFilter | 根据用户的权限来判断,是否允许通过 |
PortFilter | 根据用户的端口号来判断,是否允许通过 |
HttpMethodPermissionFilter | 根据用户的请求方法来判断,是否允许通过 |
RolesAuthorizationFilter | 根据用户的角色来判断,是否允许通过 |
SslFilter | 用户只有https请求,才会允许通过 |
Filter 创建
ShiroFilterFactoryBean
负责创建并且始化主 Filter,我们在使用 spring 配置时,都是修改它的参数来完成。下面是它的createInstance
方法,
|
|
上述返回了一个SpringShiroFilter,它非常简单,只是简单的继承了AbstractShiroFilter
。创建过程中最为核心的部分是FilterChainManager
的创建,通过设置filters
和filterChainDefinitionMap
哈希表,完成配置化。
|
|