项目源码: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]这个节点下面的部分。

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

其中

  1. /admin/**=authc,resourceCheckFilter

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

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

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

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

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

实现:

  1. public class ResourceCheckFilter extends AccessControlFilter {
  2. private String errorUrl;
  3. public String getErrorUrl() {
  4. return errorUrl;
  5. }
  6. public void setErrorUrl(String errorUrl) {
  7. this.errorUrl = errorUrl;
  8. }
  9. private static final Logger logger = LoggerFactory.getLogger(ResourceCheckFilter.class);
  10. /**
  11. * 表示是否允许访问 ,如果允许访问返回true,否则false;
  12. * @param servletRequest
  13. * @param servletResponse
  14. * @param o 表示写在拦截器中括号里面的字符串 mappedValue 就是 [urls] 配置中拦截器参数部分
  15. * @return
  16. * @throws Exception
  17. */
  18. @Override
  19. protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
  20. Subject subject = getSubject(servletRequest,servletResponse);
  21. String url = getPathWithinApplication(servletRequest);
  22. logger.debug("当前用户正在访问的 url => " + url);
  23. return subject.isPermitted(url);
  24. }
  25. /**
  26. * onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回 true 表示需要继续处理;如果返回 false 表示该拦截器实例已经处理了,将直接返回即可。
  27. * @param servletRequest
  28. * @param servletResponse
  29. * @return
  30. * @throws Exception
  31. */
  32. @Override
  33. protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
  34. logger.debug("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied ");
  35. HttpServletRequest request =(HttpServletRequest) servletRequest;
  36. HttpServletResponse response =(HttpServletResponse) servletResponse;
  37. response.sendRedirect(request.getContextPath() + this.errorUrl);
  38. // 返回 false 表示已经处理,例如页面跳转啥的,表示不在走以下的拦截器了(如果还有配置的话)
  39. return false;
  40. }
  41. }

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

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

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

获得认证主体:

  1. Subject subject = getSubject(servletRequest,servletResponse);

与 
获得当前请求的 url

  1. String url = getPathWithinApplication(servletRequest);

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

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

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

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

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

权限信息: 

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

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

  1. public class UrlPermissionResolver implements PermissionResolver {
  2. private static final Logger logger = LoggerFactory.getLogger(UrlPermissionResolver.class);
  3. /**
  4. * 经过调试发现
  5. * subject.isPermitted(url) 中传入的字符串
  6. * 和自定义 Realm 中传入的权限字符串集合都要经过这个 resolver
  7. * @param s
  8. * @return
  9. */
  10. @Override
  11. public Permission resolvePermission(String s) {
  12. logger.debug("s => " + s);
  13. if(s.startsWith("/")){
  14. return new UrlPermission(s);
  15. }
  16. return new WildcardPermission(s);
  17. }
  18. }

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

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

  1. public class UrlPermission implements Permission {
  2. private static final Logger logger = LoggerFactory.getLogger(UrlPermission.class);
  3. // 在 Realm 的授权方法中,由数据库查询出来的权限字符串
  4. private String url;
  5. public UrlPermission(String url){
  6. this.url = url;
  7. }
  8. /**
  9. * 一个很重要的方法,用户判断 Realm 中设置的权限和从数据库或者配置文件中传递进来的权限信息是否匹配
  10. * 如果 Realm 的授权方法中,一个认证主体有多个权限,会进行遍历,直到匹配成功为止
  11. * this.url 是在遍历状态中变化的
  12. *
  13. * urlPermission.url 是从 subject.isPermitted(url)
  14. * 传递到 UrlPermissionResolver 中传递过来的,就一个固定值
  15. *
  16. * @param permission
  17. * @return
  18. */
  19. @Override
  20. public boolean implies(Permission permission) {
  21. if(!(permission instanceof UrlPermission)){
  22. return false;
  23. }
  24. //
  25. UrlPermission urlPermission = (UrlPermission)permission;
  26. PatternMatcher patternMatcher = new AntPathMatcher();
  27. logger.debug("this.url(来自数据库中存放的通配符数据),在 Realm 的授权方法中注入的 => " + this.url);
  28. logger.debug("urlPermission.url(来自浏览器正在访问的链接) => " + urlPermission.url);
  29. boolean matches = patternMatcher.matches(this.url,urlPermission.url);
  30. logger.debug("matches => " + matches);
  31. return matches;
  32. }
  33. }

重点说明:如果在自定义的 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. Python数据分析基础——读写CSV文件

    1.基础python代码: #!/usr/bin/env python3 # 可以使脚本在不同的操作系统之间具有可移植性 import sys # 导入python的内置sys模块,使得在命令行中向脚 ...

  2. docker镜像与docker容器的区别

    镜像的一个实例称为容器. 你有一个镜像,这是你描述的一组图层. 如果你开始这个镜像,你有一个运行这个镜像的容器. 您可以拥有许多相同镜像的正在运行的容器. docker images 查看所有镜像 d ...

  3. 微信Oauth2.0网页开放授权

    网页授权获取用户基本信息 如果用户在微信中(Web微信除外)访问公众号的第三方网页,公众号开发者可以通过此接口获取当前用户基本信息(包括昵称.性别.城市.国家).利用用户信息,可以实现体验优化.用户来 ...

  4. nyoj 题目6 喷水装置

    喷水装置(一) 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 现有一块草坪,长为20米,宽为2米,要在横中心线上放置半径为Ri的喷水装置,每个喷水装置的效果都会让以 ...

  5. include和require的区别误区

    面试时总会被问到include和require的区别,回答的时候一般也是有以下几种区别: 1.include引入文件的时候,如果碰到错误,会给出警告,并继续运行下边的代码. require引入文件的时 ...

  6. 利用ROS内建SLAM建立地图

    ros中建地图方式有两种: 首先1.首先下载hector_slam包到你工作空间的src下 命令: cd ~/catkin/src git clone https://github.com/tu-da ...

  7. Gym 100971C 水&愚&三角形

    Description standard input/output Announcement   Statements There is a set of n segments with the le ...

  8. THUWC 2018(游记)

    这次是在雅礼洋湖中学举行的,一所2017年才创办的学校,新的学校, 貌似有些危险,积雪过多屋顶上的冰块砸下来,很容易砸到人, 听说最近就有一个人被砸死了. Day1 昨天睡的比较迟,12点吧,今天早上 ...

  9. angular 右击事件的写法

    .directive('ngRightClick', function ($parse){ return function (scope, element, attrs){ var fn = $par ...

  10. [LeetCode] Same Tree 深度搜索

    Given two binary trees, write a function to check if they are equal or not. Two binary trees are con ...