安全认证框架Shiro(三)- 源码角度解析shiro的权限验证

安全认证框架Shiro(三)- 源码角度解析shiro的权限验证

第一:前言

承接上篇文章安全认证框架Shiro (二)- shiro过滤器工作原理
权限验证,一直是个难点,看起来是个很高大尚的概念,其实我理解的权限其实就是字符串,把它当成字符串,只要和用户对应的字符串匹配成功,则验证通过。验证权限可以理解为字符串匹配即可通过授权,url对应的权限是个字符串数组集合,所以同一个请求可能配置成多个权限,比如老板和总经理都可以查询员工的信息。访问该url需要一组权限集,只要能从realm里取到你拥有的权限,只要某个匹配即可授权通过。

第二:走下去

权限验证和身份验证前半部分流程一样直到走到PathMatchingFilter.onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)这个方法都是一样的。
onPreHandler是处理权限验证的部分,不同的权限验证有一同的实现方式。
权限验证器包括如下:
这里写图片描述
其中较常用的是PermissionsAuthorizationFilter权限过滤器,如果自定义权限验证,一般继承自AuthorizationFilter。

前面流程这里不做介绍,不懂的可看安全认证框架Shiro (二)- shiro过滤器工作原理
从AccessControlFilter.onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)开始,不同类型过滤器不同逻辑。
参数mappedValue是对应的权限字符串。
如下图的“user:admin”:
这里写图片描述

我们先看看PermissionsAuthorizationFilter和FormAuthenticationFilter的appliedPaths数据有什么不同?如下

FormAuthenticationFilter.appliedPaths:

{
    /login=null,
    /authenticated=null
}

PermissionsAuthorizationFilter.appliedPaths:

{
    /index.jsp=[Ljava.lang.String;@5a24afbf,
    /admin.jsp=[Ljava.lang.String;@54e35a8b
}

其中权限里key对应的value值可参考如下:
这里写图片描述

解释:
key即是对应的需要处理的url,重点是value值,权限验证的value是有值的,非null,而value值即是权限字符串。
收到一个请求,先找到url对应的过滤器链,然后按序执行过滤器,同时mappedValue值也会一同传下去,通过realm查找用户对应的权限与mappedValue值比对,比对成功则继承下个过滤器验证。

讲那么多废话,下面上代码:
PermissionsAuthorizationFilter.isAccessAllowed():

 public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        //取得subject
        Subject subject = getSubject(request, response);
        //取的访问该url需要的权限,比如:[user:admin],注意是字符串数组
        String[] perms = (String[]) mappedValue;

        boolean isPermitted = true;
        if (perms != null && perms.length > 0) {
            if (perms.length == 1) {
                //单个权限验证,
                if (!subject.isPermitted(perms[0])) {
                    isPermitted = false;
                }
            } else {
                //多个权限验证
                if (!subject.isPermittedAll(perms)) {
                    isPermitted = false;
                }
            }
        }

        return isPermitted;
    }

subject.isPermitted()方法先验证是否登陆过,没登陆过的用户根本没必要权限验证,直接失败返回false。若登陆过,通过securityManager.isPermitted(getPrincipals(), permission);执行从数据库或缓存查询用户权限操作,和permission比对(注意这时候permission还是类似“user:admin”的字符串),继续往下走直到AuthorizingRealm.isPermitted()方法:

 public boolean isPermitted(PrincipalCollection principals, Permission permission) {
         //获得验证,角色等操作,即数据提供源
        AuthorizationInfo info = getAuthorizationInfo(principals);
        return isPermitted(permission, info);
    }

调用getAuthorizationInfo()拿到AuthorizationInfo对象:

protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {

        if (principals == null) {
            return null;
        }

        AuthorizationInfo info = null;

        if (log.isTraceEnabled()) {
            log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");
        }

        Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
        if (cache != null) {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
            }
            Object key = getAuthorizationCacheKey(principals);
            info = cache.get(key);
            if (log.isTraceEnabled()) {
                if (info == null) {
                    log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
                } else {
                    log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
                }
            }
        }


        if (info == null) {
            // Call template method if the info was not found in a cache
            info = doGetAuthorizationInfo(principals);
            // If the info is not null and the cache has been created, then cache the authorization info.
            if (info != null && cache != null) {
                if (log.isTraceEnabled()) {
                    log.trace("Caching authorization info for principals: [" + principals + "].");
                }
                Object key = getAuthorizationCacheKey(principals);
                cache.put(key, info);
            }
        }

        return info;
    }

getAuthorizationInfo先从缓存管理器里拿权限组,存在直接返回,如果没有则调用Realm实现子类的doGetAuthorizationInfo()方法,一般自定义的Reaml基本上都是继承AuthorizingRealm类,而且需要实现doGetAuthorizationInfo()方法,该方法从数据库查询数据组装到AuthorizationInfo子类对象里返回,且会保存到缓存里供下次调用。
查询出来的权限字符会和url需要的权限字符串比较,比较方法如下isPermitted():

 private boolean isPermitted(Permission permission, AuthorizationInfo info) {
        Collection<Permission> perms = getPermissions(info);
        if (perms != null && !perms.isEmpty()) {
            for (Permission perm : perms) {
                if (perm.implies(permission)) {
                    return true;
                }
            }
        }
        return false;
    }

如果匹配成功则有权限访问该url,否则无权限。

shiro没配置权限验证的Url怎么处理

在shiro里,如果没配置url过滤器,默认url在shiro里不会做任何验证的,会走spring过虑器。
看看AbstractShiroFilter.getExecutionChain()查询Url对应的过滤器链方法。默认是origChain,如果在过滤器管理器里找到再重新赋值,否则是默认值

 protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
         //默认过滤器,即ApplicationFilterChain
        FilterChain chain = origChain;

        FilterChainResolver resolver = getFilterChainResolver();
        if (resolver == null) {
            log.debug("No FilterChainResolver configured.  Returning original FilterChain.");
            return origChain;
        }

        FilterChain resolved = resolver.getChain(request, response, origChain);
        if (resolved != null) {
            log.trace("Resolved a configured FilterChain for the current request.");
            //不为null才赋值,否则取默认值
            chain = resolved;
        } else {
            log.trace("No FilterChain configured for the current request.  Using the default.");
        }

        return chain;
    }

结论

shiro在启动后,权限过滤器会维护一个类似

{
    /index.jsp=[Ljava.lang.String;@5a24afbf,
    /admin.jsp=[Ljava.lang.String;@54e35a8b
}

的集合,当接收到请求后,拿key对应的value值去与缓存或数据库里用户的权限对比。我们注意到,事先权限过滤器已经知道访问某个url对应哪个权限了,这是重点,慢慢品位。

未完待续….

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值