2

背景:使用SpringBoot + shiro + vue 搭建一个权限控制的系统

问题: 遇到的一些可能感兴趣的问题
1、doGetAuthorizationInfo不会被调用
2、自定义过滤器导致访问资源认证出现混乱(过滤器失效)
3、使用shiro进行动态的权限配置
4、更改用户角色,如何让shiro知道并在下一次访问能做出调整
5、前后端分离如何做到让unauthorizedUrl和loginUrl能访问到正确的路径(交由vue前端控制)
6、定义一个过滤器进行授权校验

问题一、doGetAuthorizationInfo不会被调用

如果是采用注解或者配置文件写死那么不会这么纠结,如果是一个beginner并且没学会跑就要飞,那么肯定在doGetAuthorizationInfo不调用吃过很大的苦头,官网指出了有几种途径可以让doGetAuthorizationInfo执行,这里需要动态权限,所以采用编码的方式
clipboard.png

角色检查

//get the current Subject 
Subject currentUser = SecurityUtils.getSubject();

if (currentUser.hasRole("administrator")) {
    //show a special button‏
} else {
    //don’t show the button?)‏
}

权限检查

If (currentUser.isPermitted(printPermission)) {
    //do one thing (show the print button?)‏
} else {
    //don’t show the button?
}

上面两种hasRole/isPermitted都会触发doGetAuthorizationInfo调用,但是这种触发方式并不是很好,因为多角色有相同的资源路径,会多次调用doGetAuthorizationInfo影响性能
具体参考文章Shiro授权链接shiro动态权限

自定义过滤器导致访问资源认证出现混乱(过滤器失效)

下面代码重写过滤器的时候需要特别注意要用new CustomRolesAuthorizationFilter() 这么创建过滤器而不是使用@Bean,具体原因很复杂,可以看看这篇文章为什么这样子
然后讲讲这篇文章没有讲到的内容,为什么会这样? springboot使用ApplicationFilterChain管理过滤器,shiro的11个默认过滤器和自定义shiro过滤器(自定义过滤器可以覆盖默认过滤器,因为默认过滤器先加入Map缓存)是放在一个名为shiroFilter的对象交由ApplicationFilterChain管理,如果给自定义过滤器加上@Bean,那么springboot会将这些自定义的过滤器放到ApplicationFilterChain上管理。
有什么问题? 简单说就是shiroFilter对象执行完里面的shiro过滤器以后(login/xx = anon验证通过了)会继续执行ApplicationFilterChain对象剩余的过滤器,而自定义过滤器(重写authc也就是FormAuthenticationFilter)刚好就是这些剩余的过滤器中的一个,这时会这个login/xx又会调用自定义过滤器再验证一遍,这导致授权不通过。

/**
ProxiedFilterChain类doFilter方法可以了解一下
作用执行完当前shiro过滤器后回到springboot的过滤器

*/
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (this.filters == null || this.filters.size() == this.index) {
            //we've reached the end of the wrapped chain, so invoke the original one:
            if (log.isTraceEnabled()) {
                log.trace("Invoking original filter chain.");
            }
            this.orig.doFilter(request, response);
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Invoking wrapped filter at index [" + this.index + "]");
            }
            this.filters.get(this.index++).doFilter(request, response, this);
        }
    }
/**
其实不用重写ShiroFilterFactoryBean,下面一个bean了解一下
*/
@Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, ShiroService shiroService) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
         // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置无权限时跳转的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/login/unAuthor.do");

        // 重写roles拦截规则 path = roles['role,role2']
        Map<String, Filter> extraFilter = shiroFilterFactoryBean.getFilters();
        extraFilter.put("roles", new CustomRolesAuthorizationFilter());
        extraFilter.put("authc", new CustomFormAuthenticationFilter());
        shiroFilterFactoryBean.setFilters(extraFilter);
        // 加载拦截资源
        shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroService.loadFilterChainDefinitions());
        return shiroFilterFactoryBean;
    }

使用shiro进行动态的权限配置

参考:shiro动态权限

更改用户角色,如何让shiro知道并在下一次访问能做出调整

写个service交由spring管理,提供加载清除shiro缓存的授权内容的功能

public void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean) {
        synchronized (this) {
            AbstractShiroFilter shiroFilter;
            try {
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();
            } catch (Exception e) {
                throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
            }

            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();

            // 清空老的权限控制
            manager.getFilterChains().clear();

            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
            shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions());
            // 重新构建生成
            Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue().trim()
                        .replace(" ", "");
                manager.createChain(url, chainDefinition);
            }
        }
    }

前后端分离如何做到让unauthorizedUrl和loginUrl能访问到正确的路径(交由vue前端控制)

最简单做法就是指定一个能访问后台的路径,比如 shiroFilterFactoryBean.setUnauthorizedUrl("/login/unAuthor.do");,然后写一个controller方法返回状态码,交给前端路由axios的拦截器进行拦截处理跳转。当然这里对登录处理可以重写FormAuthenticationFilter,在这个类中进行状态码的返回

定义一个过滤器进行授权校验

public class CustomRolesAuthorizationFilter extends AuthorizationFilter {

    private HttpServletRequest request;

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        CmsUserEntity user = (CmsUserEntity) subject.getPrincipals().getPrimaryPrincipal();

        if (null == user) {
            return true;
        }
        String[] rolesArray = (String[]) mappedValue;
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }
        List<String> rolesList = Arrays.asList(rolesArray);
        UserBiz userBiz = SpringUtil.getBean(UserBiz.class);
        List<String> roleNames = userBiz.findRoleNameByUserId(user.getId());
        Set<String> roleSet = new HashSet<>(roleNames);

        boolean disjoint = Collections.disjoint(roleSet, rolesList);
        return !disjoint;
    }
}

彻底地学
124 声望4 粉丝

引用和评论

0 条评论