《跟我学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" 转载请保留原文链接及作者。