《跟我学Shiro》笔记08-拦截器机制
原文地址:第八章 拦截器机制——《跟我学Shiro》
目录贴: 跟我学Shiro目录贴
源码:https://github.com/zhangkaitao/shiro-example
拦截器介绍
Shiro 使用了与 Servlet
一样的 Filter
接口进行扩展;所以如果对 Filter
不熟悉可以参考《Servlet3.1规范》http://www.iteye.com/blogs/subjects/Servlet-3-1 了解 Filter
的工作原理。首先下图是 Shiro 拦截器的基础类图:
NameableFilter
NameableFilter
给 Filter
起个名字,如果没有设置默认就是 FilterName
;还记得之前的如 authc
吗?当我们组装拦截器链时会根据这个名字找到相应的拦截器实例;
OncePerRequestFilter
OncePerRequestFilter
用于防止多次执行 Filter
的;也就是说一次请求只会走一次拦截器链;另外提供 enabled
属性,表示是否开启该拦截器实例,默认 enabled=true
表示开启,如果不想让某个拦截器工作,可以设置为 false 即可。
ShiroFilter
ShiroFilter
是整个 Shiro 的入口点,用于拦截需要安全控制的请求进行处理,这个之前已经用过了。
AdviceFilter
AdviceFilter
提供了 AOP 风格的支持,类似于 SpringMVC
中的 Interceptor
:
boolean preHandle(ServletRequest request, ServletResponse response) throws Exception
void postHandle(ServletRequest request, ServletResponse response) throws Exception
void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception;
preHandler
:类似于 AOP 中的前置增强;在拦截器链执行之前执行;如果返回 true 则继续拦截器链;否则中断后续的拦截器链的执行直接返回;进行预处理(如基于表单的身份验证、授权)postHandle
:类似于 AOP 中的后置返回增强;在拦截器链执行完成后执行;进行后处理(如记录执行时间之类的);afterCompletion
:类似于 AOP 中的后置最终增强;即不管有没有异常都会执行;可以进行清理资源(如接触Subject
与线程的绑定之类的);
PathMatchingFilter
PathMatchingFilter
提供了基于 Ant
风格的请求路径匹配功能及拦截器参数解析的功能,如 roles[admin,user]
自动根据 ,
分割解析到一个路径参数配置并绑定到相应的路径:
boolean pathsMatch(String path, ServletRequest request)
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception
pathsMatch
:该方法用于path
与请求路径进行匹配的方法;如果匹配返回 true;onPreHandle
:在preHandle
中,当pathsMatch
匹配一个路径后,会调用opPreHandler
方法并将路径绑定参数配置传给mappedValue
;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回 false 中断流程;默认返回 true;也就是说子类可以只实现onPreHandle
即可,无须实现preHandle
。如果没有path
与请求路径匹配,默认是通过的(即preHandle
返回 true)。
AccessControlFilter
AccessControlFilter
提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:
abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
isAccessAllowed
:表示是否允许访问;mappedValue
就是[urls]
配置中拦截器参数部分,如果允许访问返回 true,否则 false;onAccessDenied
:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。
onPreHandle
会自动调用这两个方法决定是否继续处理:
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
另外 AccessControlFilter
还提供了如下方法用于处理如登录成功后/重定向到上一个请求:
void setLoginUrl(String loginUrl) // 身份验证时使用,默认 /login.jsp
String getLoginUrl()
Subject getSubject(ServletRequest request, ServletResponse response) // 获取 Subject 实例
boolean isLoginRequest(ServletRequest request, ServletResponse response) // 当前请求是否是登录请求
// 将当前请求保存起来并重定向到登录页面
void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException
void saveRequest(ServletRequest request) // 将请求保存起来,如登录成功后再重定向回该请求
void redirectToLogin(ServletRequest request, ServletResponse response) // 重定向到登录页面
比如基于表单的身份验证就需要使用这些功能。
到此基本的拦截器就完事了,如果我们想进行访问的控制就可以继承 AccessControlFilter
;如果我们要添加一些通用数据我们可以直接继承 PathMatchingFilter
。
拦截器链
Shiro 对 Servlet
容器的 FilterChain
进行了代理,即 ShiroFilter
在继续 Servlet
容器的 Filter
链的执行之前,通过 ProxiedFilterChain
对 Servlet
容器的 FilterChain
进行了代理;即先走 Shiro 自己的 Filter
体系,然后才会委托给 Servlet
容器的 FilterChain
进行 Servlet
容器级别的 Filter
链执行;Shiro 的 ProxiedFilterChain
执行流程:
- 先执行 Shiro 自己的
Filter
链; - 再执行
Servlet
容器的Filter
链(即原始的Filter
)。
而 ProxiedFilterChain
是通过 FilterChainResolver
根据配置文件中 [urls]
部分是否与请求的 URL
是否匹配解析得到的。
FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);
即传入原始的 chain
得到一个代理的 chain
。
Shiro 内部提供了一个路径匹配的 FilterChainResolver
实现:PathMatchingFilterChainResolver
,其根据 [urls]
中配置的 url
模式(默认 Ant
风格)= 拦截器链和请求的 url
是否匹配来解析得到配置的拦截器链的;而 PathMatchingFilterChainResolver
内部通过 FilterChainManager
维护着拦截器链,比如 DefaultFilterChainManager
实现维护着 url
模式与拦截器链的关系。因此我们可以通过 FilterChainManager
进行动态动态增加 url
模式与拦截器链的关系。
DefaultFilterChainManager
会默认添加 org.apache.shiro.web.filter.mgt.DefaultFilter
中声明的拦截器:
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);
}
如果要注册自定义拦截器,IniSecurityManagerFactory
/WebIniSecurityManagerFactory
在启动时会自动扫描 ini
配置文件中的 [filters]/[main]
部分并注册这些拦截器到 DefaultFilterChainManager
;且创建相应的 url
模式与其拦截器关系链。如果使用 Spring
后续章节会介绍如果注册自定义拦截器。
如果想自定义 FilterChainResolver
,可以通过实现 WebEnvironment
接口完成:
public class MyIniWebEnvironment extends IniWebEnvironment {
@Override
protected FilterChainResolver createFilterChainResolver() {
// 在此处扩展自己的 FilterChainResolver
return super.createFilterChainResolver();
}
}
FilterChain
之间的关系。如果想动态实现 url-拦截器的注册,就可以通过实现此处的 FilterChainResolver
来完成,比如:
public class MyIniWebEnvironment extends IniWebEnvironment {
@Override
protected FilterChainResolver createFilterChainResolver() {
// 在此处扩展自己的 FilterChainResolver
// 1、创建 FilterChainResolver
PathMatchingFilterChainResolver filterChainResolver = new PathMatchingFilterChainResolver();
// 2、创建 FilterChainManager
DefaultFilterChainManager filterChainManager = new DefaultFilterChainManager();
// 3、注册 Filter
for(DefaultFilter filter : DefaultFilter.values()) {
filterChainManager.addFilter(filter.name(), (Filter) ClassUtils.newInstance(filter.getFilterClass()));
}
// 4、注册 URL-Filter 的映射关系
filterChainManager.addToChain("/login.jsp", "authc");
filterChainManager.addToChain("/unauthorized.jsp", "anon");
filterChainManager.addToChain("/**", "authc");
filterChainManager.addToChain("/**", "roles", "admin");
// 5、设置 Filter 的属性
FormAuthenticationFilter authcFilter = (FormAuthenticationFilter)filterChainManager.getFilter("authc");
authcFilter.setLoginUrl("/login.jsp");
RolesAuthorizationFilter rolesFilter = (RolesAuthorizationFilter)filterChainManager.getFilter("roles");
rolesFilter.setUnauthorizedUrl("/unauthorized.jsp");
filterChainResolver.setFilterChainManager(filterChainManager);
return filterChainResolver;
// return super.createFilterChainResolver();
}
}
此处自己去实现注册 filter
,及 url
模式与 filter
之间的映射关系。可以通过定制 FilterChainResolver
或 FilterChainManager
来完成诸如动态 URL
匹配的实现。
然后再 web.xml
中进行如下配置 Environment
:
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>com.github.zhangkaitao.shiro.chapter8.web.env.MyIniWebEnvironment</param-value>
</context-param>
自定义拦截器
通过自定义自己的拦截器可以扩展一些功能,诸如动态 url-角色/权限访问控制的实现、根据 Subject
身份信息获取用户信息绑定到 Request
(即设置通用数据)、验证码验证、在线用户信息的保存等等,因为其本质就是一个 Filter
;所以 Filter
能做的它就能做。
扩展 OncePerRequestFilter
OncePerRequestFilter
保证一次请求只调用一次 doFilterInternal
,即如内部的 forward
不会再多执行一次 doFilterInternal
:
public class MyOncePerRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
System.out.println("=========once per request filter");
chain.doFilter(request, response);
}
}
然后再 shiro.ini
配置文件中:
[main]
myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter
;[filters]
;myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter
[urls]
/**=myFilter1
Filter
可以在 [main]
或 [filters]
部分注册,然后在 [urls]
部分配置 url
与 filter
的映射关系即可。
扩展 PathMatchingFilter
PathMatchingFilter
继承了 AdviceFilter
,提供了 url
模式过滤的功能,如果需要对指定的请求进行处理,可以扩展 PathMatchingFilter
:
public class MyPathMatchingFilter extends PathMatchingFilter {
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
System.out.println("url matches,config is " + Arrays.toString((String[])mappedValue));
return true;
}
}
[filters]
myFilter3=com.github.zhangkaitao.shiro.chapter8.web.filter.MyPathMatchingFilter
[urls]
/**= myFilter3[config]
/**
就是注册给 PathMatchingFilter
的 url
模式,config
就是拦截器的配置参数,多个之间逗号分隔,onPreHandle
使用 mappedValue
接收参数值。
默认拦截器
默认拦截器名 | 拦截器类 | 说明(括号里的表示默认值) |
---|---|---|
身份验证相关的 | ||
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 基于表单的拦截器;如 /**=authc ,如果没有登录会跳到相应的登录页面登录;主要属性:1. usernameParam :表单提交的用户名参数名( username);2. passwordParam :表单提交的密码参数名(password);3. rememberMeParam :表单提交的密码参数名(rememberMe);4. loginUrl :登录页面地址(/login.jsp );5. successUrl :登录成功后的默认重定向地址;6. failureKeyAttribute :登录失败后错误信息存储 key(shiroLoginFailure); |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | Basic HTTP 身份验证拦截器,主要属性:applicationName :弹出登录框显示的信息(application); |
logout |
org.apache.shiro.web.filter.authc.LogoutFilter | 退出拦截器,主要属性:redirectUrl :退出成功后重定向的地址(/);示例 /logout=logout |
user |
org.apache.shiro.web.filter.authc.UserFilter | 用户拦截器,用户已经身份验证/记住我登录的都可;示例 /**=user |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例 /static/**=anon |
授权相关的 | ||
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色授权拦截器,验证用户是否拥有所有角色;主要属性:loginUrl :登录页面地址(/login.jsp );unauthorizedUrl :未授权后重定向的地址;示例 /admin/**=roles[admin] |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限授权拦截器,验证用户是否拥有所有权限;属性和 roles 一样;示例 /user/**=perms["user:create"] |
port |
org.apache.shiro.web.filter.authz.PortFilter | 端口拦截器,主要属性:port (80):可以通过的端口;示例 /test= port[80] ,如果用户访问该页面是非 80,将自动将请求端口改为 80 并重定向到该 80 端口,其他路径/参数等都一样 |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | rest 风格拦截器,自动根据请求方法构建权限字符串(GET=read , POST=create ,PUT=update , DELETE=delete , HEAD=read , TRACE=read , OPTIONS=read , MKCOL=create )构建权限字符串;示例 /users=rest[user] ,会自动拼出 user:read,user:create,user:update,user:delete 权限字符串进行权限匹配(所有都得匹配,isPermittedAll); |
ssl |
org.apache.shiro.web.filter.authz.SslFilter | SSL 拦截器,只有请求协议是 https 才能通过;否则自动跳转会 https 端口(443);其他和 port 拦截器一样; |
其他 | ||
noSessionCreation |
org.apache.shiro.web.filter.session.NoSessionCreationFilter | 不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常; |
这些默认的拦截器会自动注册,可以直接在 ini 配置文件中通过 拦截器名.属性
设置其属性:
perms.unauthorizedUrl=/unauthorized
;如果某个拦截器不想使用了可以直接通过如下配置直接禁用
perms.enabled=false
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《跟我学Shiro》笔记08-拦截器机制
文章字数:2.9k
本文作者:Bin
发布时间:2018-04-15, 15:12:22
最后更新:2019-09-02, 15:02:21
原始链接:http://coolview.github.io/2018/04/15/%E8%B7%9F%E6%88%91%E5%AD%A6Shiro/%E3%80%8A%E8%B7%9F%E6%88%91%E5%AD%A6Shiro%E3%80%8B%E7%AC%94%E8%AE%B008-%E6%8B%A6%E6%88%AA%E5%99%A8%E6%9C%BA%E5%88%B6/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。