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

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

上一节,我们实现了自定义的 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,就说明该认证主体不具有访问某个资源的权限。

原文地址:https://www.cnblogs.com/Gent-Wang/p/9214998.html

时间: 2024-07-29 09:12:40

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

shiro+密码匹配器验证登陆

1.先上工具类MD5Util   目前真正用到的是直接MD5加密的方法   未使用自定义加工密码加密 package cn.cjq.util; import cn.cjq.entity.User; import java.security.MessageDigest;import java.util.Random; public class MD5Util { /** * 加工密码,和登录一致. * @param user * @return */ public static User md5P

web工程使用spring mvc+shiro进行权限控制

第1步:引入shiro相关jar包 ehcache-core-2.5.0.jar shiro-ehcache-1.2.3.jar shiro-core-1.2.3.jar shiro-web-1.2.3.jar shiro-spring-1.2.3.jar 第二步:web.xml配置 <!-- shiro的filter --> <!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 --> <fi

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

本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看源码追寻Shiro中的过滤器的实现原理. 初始化流程 ShiroFilterFactoryBean实现了FactoryBean接口,那么Spring在初始化的时候必然会调用ShiroFilterFactoryBean的getObject()获取实例,而ShiroFilterFactoryBean也在

spring boot 2 + shiro 实现权限管理

Shiro是一个功能强大且易于使用的Java安全框架,主要功能有身份验证.授权.加密和会话管理.看了网上一些文章,下面2篇文章写得不错.Springboot2.0 集成shiro权限管理 Spring Boot:整合Shiro权限框架 自己动手敲了下代码,在第一篇文章上加入了第二篇文章的Swagger测试,另外自己加入lombok简化实体类代码,一些地方代码也稍微修改了下,过程中也碰到一些问题,最终代码成功运行. 开发版本:IntelliJ IDEA 2019.2.2jdk1.8Spring B

django 自定义标签和过滤器

django 自定义标签和过滤器 Django支持自定义标签和过滤器.起初还不太重视它这项功能,但最近试了试自定义标签.发现django这个功能实在是太爽了. 首先在你项目的一个app中建立一个python源文件夹,(即文件夹里面要包含一个__init__.py.)文件夹名为templatetags.  此文件夹便是存放自定义标签和过滤器的源码的地方了. 再如果是在templatetags文件夹中定义了标签,如 test_tags.py,要如何使用我们自定义的test_tags.py呢.很简单,

shiro的权限控制应用,集成spring项目,密码错误次数过多短时间锁定

以前对shiro都是一知半解,最近系统学了一遍shiro并集成到了在做的项目中. 下面就详细向大家描述一下shiro的用法. 首先是对spring的配置文件,如下: 1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3

Rust 1.7.0 匹配器 match 的简介和使用

使用过正則表達式的人应该都知道 matcher ,通过 matcher 匹配器运算正則表達式,完毕一系列的匹配规则. 在Rust 中 没有 switch 语句.matcher 就是 switch 的一个变形,但比其它语言中的 switch 更强大! 一.简单举例说明 简单的 matcher 和 if 语句很相似,假设是简单的条件推断能够用if语句: let n = 5; if n < 0 { print!("{} is negative", n); } else if n >

J2EE监听器和过滤器基础

Servlet程序由Servlet,Filter和Listener组成,其中监听器用来监听Servlet容器上下文. 监听器通常分三类:基于Servlet上下文的ServletContex监听,基于会话的HttpSession监听和基于请求的ServletRequest监听. ServletContex监听器 ServletContex又叫application,存在范围是整个Servlet容器生命周期,当系统启动时就会创建,系统关闭时会销毁,该对象通常存放一些非常通用的数据,但是不推荐存放太多

django 内建标签和过滤器参考

下面的标签和过滤器参考就是为那些没有 admin 站点的可用的人准备的.由于 Django 是高度可定制的,你的 admin 里的关于标签和过滤器的参考可以认为是最可信的. 内建标签参考 block 定义一个能被子模板覆盖的 块. 参阅 模板继承 了解更多信息 comment 注释.模板引擎会忽略掉 {% comment %} 和 {% endcomment %} 之间的所有内容. cycle 在循环时轮流使用给定的字符串列表中的值. 在一个循环中, 在循环过程中的每次循环里轮流使用给定的字符串