项目源码: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. 判断 js 数组是否为 empty

    function isEmpty(obj) { for(var key in obj) { if(obj.hasOwnProperty(key)) return false; } return tru ...

  2. 构建乘积数组--java

    题目:给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1].不能使 ...

  3. 【.NET MVC分页】.NET MVC 使用pagelist 分页

    1.安装 2. 在NuGet程序包管理控制台 ,输入Install-Package PagedList.mvc 安装PagedList 和PageList.Mvc; 3. @{ Layout = nu ...

  4. BZOJ 1192:[HNOI2006]鬼谷子的钱袋(数学)

    鬼谷子的钱袋Description鬼谷子非常聪明,正因为这样,他非常繁忙,经常有各诸侯车的特派员前来向他咨询时政.有一天,他在咸阳游历的时候,朋友告诉他在咸阳最大的拍卖行(聚宝商行)将要举行一场拍卖会 ...

  5. live-server 快速搭建静态服务

    作为切图仔呢,平时都是在修改页面样式.展示后台返回的数据.调试js效果,这时候呢就需要搭一个服务器以便我们调试.平时我一般用的都是wamp,在www目录下新建项目然后开始撸代码,不过有时候呢不太方便, ...

  6. idea下载多个插件项目启动不了解决方案

    今天下载mybatis plugin插件的时候 有好多个版本的plugin,然后呢,看第二个比较热门,就下载了第二个,然后重启idea发现这个插件貌似得花钱,那算了吧,咱用第一个免费的吧,就又下载了第 ...

  7. nginx进行项目域名配置时提示Job for nginx.service failed

    ps aux | grep nginx /bin/systemctl stop nginx.service /bin/systemctl start nginx.service /bin/system ...

  8. Windows下C++多线程同步与互斥简单运用(转)

    1.  互斥量,Mutex #include <Windows.h> #include <iostream> using namespace std; DWORD WINAPI ...

  9. C语言中的基础知识变量探讨

    C语言中的变量是编程的基础,主要有四个要素:存储类型.存储大小.存储名称和存储地址. 一.变量的要素: 1.存储类型:主要表明名变量存储的特征,主要有auto.extern.static和regist ...

  10. linux根文件系统制作之busybox编译和系统构建【转】

    转自:http://blog.chinaunix.net/uid-29401328-id-5019660.html 介绍完相关文件后我们开始构建文件系统,涉及到的文件等到具体用到的时候再讲. 一.编译 ...