SSM + Shiro 整合 (5)- 自定义过滤器及权限解析器、介绍权限匹配流程

23 篇文章 0 订阅
17 篇文章 3 订阅

项目源码: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,就说明该认证主体不具有访问某个资源的权限。

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
SSMSpring+SpringMVC+MyBatis)是一种Java Web开发框架,被广泛使用于企业级应用的开发中。Spring提供了依赖注入和面向切面编程的特性,Spring MVC是一种轻量级的Web框架,而MyBatis则是一种持久层框架,可以方便地操作数据库。 Layui是一个简单易用、高效轻量级的前端框架,可以帮助前端开发者快速搭建Web界面。Layui提供了丰富的UI组件和灵活的样式定制功能,能够帮助开发者实现页面的快速展示和交互。 Shiro是一个强大且易于使用的Java安全框架,提供了认证、授权、会话管理和加密等安全控制功能。Shiro能够帮助开发者轻松地实现用户身份验证和访问控制,提高系统的安全性。 综合上述三者,使用SSM框架可以简化Java Web的开发流程,通过Spring提供的依赖注入和面向切面编程,我们能够更好地管理和维护项目的各个组件。使用SpringMVC可以方便地开发出符合MVC设计模式的Web应用程序,而MyBatis则提供了对数据库的便捷访问,可以避免直接编写繁琐的SQL语句。 在前端方面,Layui的轻量级特性和丰富的UI组件能够加快前端开发的速度,使页面的搭建变得简单高效。 而使用Shiro可以保护我们的应用程序,通过身份验证和访问控制,可以确定用户的身份并限制其权限,提高系统的安全性。 总的来说,SSM、Layui和Shiro三者结合使用可以提高我们开发的效率和安全性,同时也能够提供用户友好的界面体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值