项目源码:https://github.com/weimingge14/Shiro-project
演示地址:http://liweiblog.duapp.com/Shiro-project/login

关于 Shiro 的权限匹配器和过滤器

上一节,我们实现了自定义的 Realm,方式是继承 AuthorizingRealm这个抽象类,分别实现认证的方法和授权的方法。

这一节实现的代码的执行顺序: 
1、Shiro定义的过滤器和自定义的过滤器,在自定义的过滤器中执行 Subject对象的判断是否具有某项权限的方法 isPermitted(),传入某一个跟当前登录对象相关的特征值(这里是登录对象正在访问的 url 连接) 
2、程序走到自定义的 Realm 中的授权方法中,根据已经认证过的主体查询该主体具有的角色和权限字符串,通常情况下是一个角色的集合和一个权限的集合。

此时我们第 1 步有一个字符串,第 2 步有一个字符串的集合(权限的集合)。 
程序要帮我们做的就是看第 1 步的字符串在不在第 2 步的字符串集合中。那么这件事情是如何实现的呢?

3、此时程序检测到配置文件中有声明一个实现了 PermissionResolver 的类,这个时候程序就会到这个类中去查找所采用的权限匹配策略。 
4、到上一步返回的实现了 Permission 的类的对象中的 implies()方法中去进行判断。如果第 2 步的权限字符串数量多于 1 个,这个 implies()就会执行多次,直到该方法返回 true 为止,第 1 步的 isPermitted() 才会返回 true。

下面我们来关注一下 [urls]这个节点下面的部分。

[urls]
# 配置 url 与使用的过滤器之间的关系
/admin/**=authc,resourceCheckFilter
/login=anon

其中

/admin/**=authc,resourceCheckFilter

表示,当请求 /admin/** 的时候,会依次经过 (1)authc和 (2)resourceCheckFilter 这两个过滤器。

过滤器在有些地方也叫拦截器,他们的意思是一样的。

(1)authc这个过滤器是 Shiro 自定义的认证过滤器,即到自定义 Realm 的认证方法里面去按照指定的规则进行用户名和密码的匹配。 
DefaultFilter这个枚举类里面定义了多个自定义的过滤器,可以直接使用。

(2)resourceCheckFilter 是一个自定义的过滤器,我们来看看它的声明:

[filters]
# 声明一个自定义的过滤器
resourceCheckFilter = com.liwei.shiro.filter.ResourceCheckFilter
# 为上面声明的自定义过滤器注入属性值
resourceCheckFilter.errorUrl=/unAuthorization

实现:

public class ResourceCheckFilter extends AccessControlFilter {

    private String errorUrl;

    public String getErrorUrl() {
return errorUrl;
} public void setErrorUrl(String errorUrl) {
this.errorUrl = errorUrl;
} private static final Logger logger = LoggerFactory.getLogger(ResourceCheckFilter.class); /**
* 表示是否允许访问 ,如果允许访问返回true,否则false;
* @param servletRequest
* @param servletResponse
* @param o 表示写在拦截器中括号里面的字符串 mappedValue 就是 [urls] 配置中拦截器参数部分
* @return
* @throws Exception
*/
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
Subject subject = getSubject(servletRequest,servletResponse);
String url = getPathWithinApplication(servletRequest);
logger.debug("当前用户正在访问的 url => " + url);
return subject.isPermitted(url);
} /**
* onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。 * @param servletRequest
* @param servletResponse
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
logger.debug("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied "); HttpServletRequest request =(HttpServletRequest) servletRequest;
HttpServletResponse response =(HttpServletResponse) servletResponse;
response.sendRedirect(request.getContextPath() + this.errorUrl); // 返回 false 表示已经处理,例如页面跳转啥的,表示不在走以下的拦截器了(如果还有配置的话)
return false;
}
}

注意:我们首先要关注 isAccessAllowed()方法,在这个方法中,如果返回 true,则表示“通过”,走到下一个过滤器。如果没有下一个过滤器的话,表示具有了访问某个资源的权限。如果返回 false,则会调用 onAccessDenied 方法,去实现相应的当过滤不通过的时候执行的操作,例如跳转到某一个指定的登录页面,去引导用户输入另一个具有更大权限的用户名和密码进行登录。

isAccessAllowed()方法的最后一个参数 o,可以获得我们自定义的过滤器后面中括号中所带的参数。

我们再跳回到 isAccessAllowed()中:subject.isPermitted(url)。说明通过继承 AccessControlFilter我们可以得到认证主体 Subject和当前请求的 url 链接,它们的 API 分别是:

获得认证主体:

Subject subject = getSubject(servletRequest,servletResponse);

与 
获得当前请求的 url

String url = getPathWithinApplication(servletRequest);

然后,我们调用了 subject.isPermitted(url)方法,将 url 这个字符串对象传入。

此时我们的流程应该走到 Realm的授权方法中,通过查询(经过了认证的)用户信息去查询该用户具有的权限信息。此时的代码走到了这里。 
在授权方法中,我们看到 SimpleAuthorizationInfo的角色信息和权限信息都是通过字符串来解析的。 
角色信息和权限信息都是集合。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("--- MyRealm doGetAuthorizationInfo ---"); // 获得经过认证的主体信息
User user = (User)principalCollection.getPrimaryPrincipal(); //
// 此处为节约篇幅,突出重点省略
// SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(new HashSet<>(roleSnList));
info.setStringPermissions(new HashSet<>(resStrList)); // 以上完成了动态地对用户授权
logger.debug("role => " + roleSnList);
logger.debug("permission => " + resStrList); return info;
}

在这个 Realm 的授权方法中,完成了对该认证后的主体所具有的角色和权限的查询,然后放入 SimpleAuthorizationInfo对象中。

接下来就要进行 subject.isPermitted(url)中的 url 和 自定义 Realm 中的授权方法中的 info.setStringPermissions(new HashSet<>(resStrList));权限字符串集合的匹配操作了。

权限信息: 

它们都是从数据库中查询出来的。

那么如何实现匹配呢?比较简单的一个思路就是比较字符串,但是这件简单的比较的事情被 Shiro 定义为一个 PermissionResolver,通过实现 PermissionResolver,我们可以为完成自定义的权限匹配操作,可以是简单的字符串匹配,也可以稍有灵活性的通配符匹配,这都取决于我们程序员自己。

public class UrlPermissionResolver implements PermissionResolver {

    private static final Logger logger = LoggerFactory.getLogger(UrlPermissionResolver.class);

    /**
* 经过调试发现
* subject.isPermitted(url) 中传入的字符串
* 和自定义 Realm 中传入的权限字符串集合都要经过这个 resolver
* @param s
* @return
*/
@Override
public Permission resolvePermission(String s) {
logger.debug("s => " + s); if(s.startsWith("/")){
return new UrlPermission(s);
}
return new WildcardPermission(s);
}
}

可以看到,权限信息是通过字符串:“/admin/**”等来进行匹配的。这时就不能使用 Shiro 默认的权限匹配器 WildcardPermission了。

而 UrlPermission 是一个实现了 Permission接口的类,它的 implies 方法的实现决定了权限是否匹配,所以 implies 这个方法的实现是很重要的。

public class UrlPermission implements Permission {

    private static final Logger logger = LoggerFactory.getLogger(UrlPermission.class);

    // 在 Realm 的授权方法中,由数据库查询出来的权限字符串
private String url; public UrlPermission(String url){
this.url = url;
} /**
* 一个很重要的方法,用户判断 Realm 中设置的权限和从数据库或者配置文件中传递进来的权限信息是否匹配
* 如果 Realm 的授权方法中,一个认证主体有多个权限,会进行遍历,直到匹配成功为止
* this.url 是在遍历状态中变化的
*
* urlPermission.url 是从 subject.isPermitted(url)
* 传递到 UrlPermissionResolver 中传递过来的,就一个固定值
*
* @param permission
* @return
*/
@Override
public boolean implies(Permission permission) {
if(!(permission instanceof UrlPermission)){
return false;
}
//
UrlPermission urlPermission = (UrlPermission)permission;
PatternMatcher patternMatcher = new AntPathMatcher(); logger.debug("this.url(来自数据库中存放的通配符数据),在 Realm 的授权方法中注入的 => " + this.url);
logger.debug("urlPermission.url(来自浏览器正在访问的链接) => " + urlPermission.url);
boolean matches = patternMatcher.matches(this.url,urlPermission.url);
logger.debug("matches => " + matches);
return matches;
}
}

重点说明:如果在自定义的 Realm 中的授权方法中传入的授权信息中的权限信息是一个集合,那么这里的 implies 就会进行遍历,直到这个方法返回 true 为止,如果遍历的过程全部返回 false,就说明该认证主体不具有访问某个资源的权限。

关于 Shiro 的权限匹配器和过滤器的更多相关文章

  1. 自定义shiro实现权限验证方法isAccessAllowed

    由于Shiro filterChainDefinitions中 roles默认是and, admin= user,roles[system,general] 比如:roles[system,gener ...

  2. 【shiro】(5)---基于Shiro的权限管理

    基于Shiro的权限管理项目搭建 前面写了四篇有关权限的文章,算是这篇文章的铺垫了.这篇文章采用 开发环境           JDK1.8          Eclipse          Mav ...

  3. Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理

    本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...

  4. spring boot 2 + shiro 实现权限管理

    Shiro是一个功能强大且易于使用的Java安全框架,主要功能有身份验证.授权.加密和会话管理.看了网上一些文章,下面2篇文章写得不错.Springboot2.0 集成shiro权限管理 Spring ...

  5. springboot + shiro 构建权限模块

    权限模块基本流程 权限模块的基本流程:用户申请账号和权限 -->登陆认证 -->安全管控模块认证 -->调用具体权限模块(基于角色的权限控制) --> 登陆成功 -->访 ...

  6. Shiro (包含权限满足其中一个就通过的用法)

    方法/步骤 1 web.xml添加配置 <!-- shiro过滤器 --> <filter> <filter-name>shiroFilter</filter ...

  7. spring boot + mybatis + layui + shiro后台权限管理系统

    后台管理系统 版本更新 后续版本更新内容 链接入口: springboot + shiro之登录人数限制.登录判断重定向.session时间设置:https://blog.51cto.com/wyai ...

  8. SpringBoot整合Shiro实现权限控制,验证码

    本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂. 目前我的需求是一个博客系统,有用户和管理员两种角色.一个用户可能有 ...

  9. SpringBoot整合Shiro实现权限控制

    目录 1.SpringBoot整合Shiro 1.1.shiro简介 1.2.代码的具体实现 1.2.1.Maven的配置 1.2.2.整合需要实现的类 1.2.3.项目结构 1.2.4.ShiroC ...

随机推荐

  1. 理解机器为什么可以学习(四)---VC Dimension

    前面一节我们通过引入增长函数的上限的上限,一个多项式,来把Ein 和 Eout 的差Bound住,这一节引入VC Bound进一步说明这个问题. 前边我们得到,如果一个hypethesis集是有bre ...

  2. java 课堂笔记

  3. Android简单的BaseExpandableList使用

    1.Activity package com.example.administrator.mystudent.ExpandableListView; import android.app.Expand ...

  4. HDU 4514 湫湫系列故事——设计风景线(并查集+树形DP)

    湫湫系列故事——设计风景线 Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) To ...

  5. 购物(sum)

    购物(sum) 题目描述 visit_world 有一个商店,商店里卖N个商品,第ii 个的价格为 a[[i] 我们称一个正整数K 是美妙的,当且仅当我们可以在商店里选购若干个商品,使得价格之和落在区 ...

  6. vue使用stylus

    在package.json中添加  stylus-loader "css-loader": "^0.28.0", "stylus-loader&quo ...

  7. [CODEVS1130]数字反转

    题目描述 Description 给定一个整数,请将该数各个位上数字反转得到一个新数.新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零. 这是一道水题.我必须 ...

  8. 【全局变量】mysql查看全局变量以及设置全局变量的值

    1.查看mysql的所有全局变量的值 SHOW GLOBAL VARIABLES 或者 SHOW VARIABLES mysql有很多全局变量,包括系统的一些基本信息,以及mysql的一些基本配置都可 ...

  9. LeetCode OJ-- Combination Sum II **

    https://oj.leetcode.com/problems/combination-sum-ii/ 一列数,每个数只能用一次或者不用,给出和为target的组合. 递归写的深搜,使用了编程技巧, ...

  10. js中click重复执行

    问题背景:在写一个非常简单添加方法的时候,用onclick事件调用添加方法,点击第一次没问题,第二次会重复执行,经过多次查找资料得知这个状况的解决方案,特意总结一下: 代码如下:点击#spec_for ...