博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Shiro原理解析(三)--再谈过滤器
阅读量:6232 次
发布时间:2019-06-22

本文共 13928 字,大约阅读时间需要 46 分钟。

前言

前面两篇文章主要讲了SpringShiroFilter的初始化以及doFilter方法。总结一下:初始化的主要操作是根据配置构建所有url对应的过滤链,doFilter()方法将url对应的过滤链添加到javaEE原生的的过滤器中。

本篇文章的内容

本篇文章主要解析具体的Filter是如何处理鉴权的(即如何判断某个用户是否有权限访问该url)。本篇文章一PermissionsAuthorizationFilter为例(shiro默认的拦截器有9个,包括roles,anno,perms等等)

正文

首先给出PermissionsAuthorizationFilter的类结构,该类只有一个isAccessAllowed方法

public class PermissionsAuthorizationFilter extends AuthorizationFilter {  //TODO - complete JavaDoc    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {        Subject subject = getSubject(request, response);        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;    }}复制代码

第一步,既然是过滤器,我们当然还是一步一步往上寻找doFilter()方法,最终还是在OncePerRequestFilter()方法中找到

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)            throws ServletException, IOException {        String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();        if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {            log.trace("Filter '{}' already executed.  Proceeding without invoking this filter.", getName());            filterChain.doFilter(request, response);        } else //noinspection deprecation            if (/* added in 1.2: */ !isEnabled(request, response) ||                /* retain backwards compatibility: */ shouldNotFilter(request) ) {            log.debug("Filter '{}' is not enabled for the current request.  Proceeding without invoking this filter.",                    getName());            filterChain.doFilter(request, response);        } else {            // Do invoke this filter...            log.trace("Filter '{}' not yet executed.  Executing now.", getName());            request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);            try {                doFilterInternal(request, response, filterChain);            } finally {                // Once the request has finished, we're done and we don't                // need to mark as 'already filtered' any more.                request.removeAttribute(alreadyFilteredAttributeName);            }        }    }  public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)            throws ServletException, IOException {        Exception exception = null;        try {            boolean continueChain = preHandle(request, response);            if (log.isTraceEnabled()) {                log.trace("Invoked preHandle method.  Continuing chain?: [" + continueChain + "]");            }            if (continueChain) {                executeChain(request, response, chain);            }            postHandle(request, response);            if (log.isTraceEnabled()) {                log.trace("Successfully invoked postHandle method");            }        } catch (Exception e) {            exception = e;        } finally {            cleanup(request, response, exception);        }    }复制代码

这里鉴权的核心在于doFilterInternal方法中的boolean continueChain = preHandle(request, response),如果鉴权通过则返回true,并且调用executeChain()调用下一个过滤器。(这里我找了好一番时间才发现鉴权逻辑躲在preHandle里面,抓狂,正常理解不怎么都应该在executeChain里面嘛。暗暗吐槽)

下面让我们来一层一层的剥开preHandle的外衣。

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {            if (log.isTraceEnabled()) {                log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");            }            return true;        }        for (String path : this.appliedPaths.keySet()) {            // If the path does match, then pass on to the subclass implementation for specific checks            //(first match 'wins'):            if (pathsMatch(path, request)) {                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);                Object config = this.appliedPaths.get(path);                return isFilterChainContinued(request, response, path, config);            }        }        //no path matched, allow the request to go through:        return true;    }复制代码

第一步检查appliedPaths是否为空,这里可能有小伙伴忘记appliedPaths里面存的是什么了,还是得祭出第一篇文章的dubug图。

第二步开始遍历appliedPaths的key,与request里的uri进行匹配。匹配的逻辑与第二篇文章uri匹配过滤链的逻辑一致,都是交给了默认的AntPathMatcher。

for (String path : this.appliedPaths.keySet()) {            // If the path does match, then pass on to the subclass implementation for specific checks            //(first match 'wins'):            if (pathsMatch(path, request)) {                log.trace("Current requestURI matches pattern '{}'.  Determining filter chain execution...", path);                Object config = this.appliedPaths.get(path);                return isFilterChainContinued(request, response, path, config);            }        }复制代码

这里得到了权限配置的config,这里的config对象通过debug图可看到是一个String数组,这都是在第一篇文章Filter初始化时完成的,其实里面就是我们配置的权限,例如perms[user:add,user:delete]。第三步调用isFilterChainContinued()进行鉴权

private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,                                           String path, Object pathConfig) throws Exception {        if (isEnabled(request, response, path, pathConfig)) {             return onPreHandle(request, response, pathConfig);        }        return true;    }复制代码

点开onPreHandle,注意这里是个逻辑表达式,如果前面isAccessAllowed()返回true,后面的OnAccessDenied()方法将不会执行。(短路原则)

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);    }复制代码

最终调用到我们PermissionsAuthorizationFilter的isAccessAllowed方法。getSubject得到用户,将鉴权的逻辑委派给Subject。(Subject在SpringShiroFilter中创建了,所以这里可以直接获取到,如何创建以及维护的在后面的文章讲解)

public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {        Subject subject = getSubject(request, response);        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;    }复制代码

这里首先判断有没有principals(凭证,登录时放进去的,权限是通过凭证获取的,也是识别用户的唯一标识,一般为用户名)。如果有凭证则将鉴权委托给securityManager。(shiro官网也提及到securityManager是shiro的核心,到后面会发现所有的登录,鉴权,session管理都是由它掌控的)

public boolean isPermittedAll(String... permissions) {        return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);    }复制代码

这里稍微看一下securityManager的类图结构,方便以后章节的讲解。authenticator处理登录,authorizer处理鉴权,sessionManager处理sesison管理

本篇文章涉及到的是authorizer,回头看到我们的spring-shiro.xml配置文件,我配置的是一个多认证器,并为他设置了认证策略(一个成功为成功)。

点进ModularRealmAuthenticator的isPermitted方法,第一步获取到我们所有的realms,并逐个调用isPermitted()方法,一个通过则鉴权通过

public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {        assertRealmsConfigured();        if (permissions != null && permissions.length > 0) {            for (String perm : permissions) {                if (!isPermitted(principals, perm)) {                    return false;                }            }        }        return true;    }复制代码
public boolean isPermitted(PrincipalCollection principals, Permission permission) {        assertRealmsConfigured();        for (Realm realm : getRealms()) {            if (!(realm instanceof Authorizer)) continue;            if (((Authorizer) realm).isPermitted(principals, permission)) {                return true;            }        }        return false;    }复制代码
public boolean isPermitted(PrincipalCollection principals, String permission) {        Permission p = getPermissionResolver().resolvePermission(permission);        return isPermitted(principals, p);    }复制代码

这里将权限字符串构建成了一个permission对象,这个permission对象可以执行通配符式的权限比对。然后开始获取AuthorizationInfo(授权信息)

public boolean isPermitted(PrincipalCollection principals, Permission permission) {        AuthorizationInfo info = getAuthorizationInfo(principals);        return isPermitted(permission, info);    }复制代码

注意这个getAuthorizationInfo先从缓存中获取(所以shiro是支持授权缓存的),如果缓存为空才调用doGetAuthorizationInfo获取授权。

protected AuthorizationInfo getAuthorizationInfo(PrincipalCollection principals) {        if (principals == null) {            return null;        }        AuthorizationInfo info = null;        if (log.isTraceEnabled()) {            log.trace("Retrieving AuthorizationInfo for principals [" + principals + "]");        }        Cache
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; } 复制代码

doGetAuthorizationInfo最终会调用到我们的配置的Realms类的,实现我们自己的授权逻辑

/**     * 授权,会在需要验证权限的时候被shiro调用,如果开启了缓存则只会第一次验证的时候被调用.     *     * @param principals     * @return AuthorizationInfo     * @author liuruojing     * @since ${PROJECT_NAME} 0.1.0     */    @Override    protected final AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {        String username = (String) principals.getPrimaryPrincipal();        User  user = userMapper.selectByUsername(username);        Set
roles = new HashSet<>(); //角色集合 Set
permissions = new HashSet<>(); //权限集合 //根据userId查出所拥有的roleId List
roleIds = userRoleMapper.selectByUserId(user.getUserId()); Iterator
iterator = roleIds.iterator(); while (iterator.hasNext()) { Long roleId = iterator.next(); Role role = roleMapper.selectByPrimaryKey(roleId); //将角色放入角色集合 roles.add(role.getRoleName()); //根据角色查出权限Id List
permissionIds = rolePermissionMapper.selectByRoleId(roleId); Iterator
pIterator = permissionIds.iterator(); while (pIterator.hasNext()) { Long permissionId = (Long) pIterator.next(); Permission permission = permissionMapper.selectByPrimaryKey(permissionId); //将所有权限放入权限集合 permissions.add(permission.getPermissionName()); } } SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roles); //放入角色 info.setStringPermissions(permissions); //放入权限 return info; }}复制代码

现在我们知道了访问url需要的权限,同时也查询出了此用户具有的权限,我们只需要进行比对两者便可以知道用户是否有权限访问此url了。将获取的AuthorizationInfo解析成Permission对象集合,逐个调用Permission对象的implies方法进行比对。如果比对成功则返回true执行下一个所需权限的比对。

//changed visibility from private to protected for SHIRO-332    protected boolean isPermitted(Permission permission, AuthorizationInfo info) {        Collection
perms = getPermissions(info); if (perms != null && !perms.isEmpty()) { for (Permission perm : perms) { if (perm.implies(permission)) { return true; } } } return false; }复制代码

最后所有权限比对完后会回到上面的onpreHandle(),如果isAccessAllowed鉴权通过则返回true,否则调用onAccessDenied方法设置重定向到unloginUrl或者unAuthorizedUrl,这两个url是我们在xml中配置的

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);    }复制代码
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {        Subject subject = getSubject(request, response);        // If the subject isn't identified, redirect to login URL        if (subject.getPrincipal() == null) {            saveRequestAndRedirectToLogin(request, response);        } else {            // If subject is known but not authorized, redirect to the unauthorized URL if there is one            // If no unauthorized URL is specified, just return an unauthorized HTTP status code            String unauthorizedUrl = getUnauthorizedUrl();            //SHIRO-142 - ensure that redirect _or_ error code occurs - both cannot happen due to response commit:            if (StringUtils.hasText(unauthorizedUrl)) {                WebUtils.issueRedirect(request, response, unauthorizedUrl);            } else {                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);            }        }        return false;    }复制代码

转载地址:http://sfena.baihongyu.com/

你可能感兴趣的文章
Android零基础入门第45节:GridView简单使用
查看>>
读书笔记---操作系统概论
查看>>
从ASCII聊起
查看>>
大数据和Hadoop时代的维度建模和Kimball数据集市
查看>>
Android官方入门文档[3]构建一个简单的用户界面
查看>>
编写出色CSS代码的13个建议
查看>>
Alluxio之IO选项:读写类型
查看>>
ECS centos7安装elasticsearch2.4.1填坑日记
查看>>
调查显示:企业挣扎于攻击检测和分析中
查看>>
「消失」的无人机 | IFA 2017现场直击
查看>>
VIM复制指令yank
查看>>
【网络编程6】Java与C语言套接字Socket通信的例子
查看>>
Linux常用开发服务器的代码[Linux zhoulifa ]
查看>>
通过反射克隆对象,对象复制(克隆),对象合并工具类 升级版
查看>>
企业网络安全浅析
查看>>
Oracle常用sql语句(三)之子查询
查看>>
搞定IT基础设施方案 云计算先行
查看>>
Improving (network) I/O performance ...
查看>>
sql server 2008 不允许保存更改,您所做的更改要求删除并重新创建以下表 的解决办法...
查看>>
innodb的文件组成
查看>>