美文网首页
SpringBoot整合shiro(三):鉴权流程

SpringBoot整合shiro(三):鉴权流程

作者: 圆企鹅i | 来源:发表于2021-01-09 15:21 被阅读0次

    权限控制

    (下面都是废话 ,可以直接看代码部分)
    首先我们聊一下 权限控制最基本的规范吧
    首先五张表 是基本上做鉴权分权 现在必然有的 基本上都是在这五张表上面 加加减减而已

    数据表

    user 用户
    role 角色
    permission 权限
    然后就是两个中间表
    user_role 毕竟一个用户 可以又当爹 又当妈
    role_permission 一个爹也 洗衣做饭 扣屎盆子 都得会
    至于其他的表 根据业务 自己增加分组 特殊权限关系 再改改也就行

    shiro鉴权思路

    image.png

    底层也是默认是这几个东西
    咱也可以想想
    如果是你来写权限控制 得咋写
    那可不就是拦截器呗
    访问一下 过滤一下 拦截一下请求 看看你的信息 对不对 不对不给你整
    优化的时候 整个aop 简化一下代码
    shiro也差不多整个思路
    只不过shiro整了大量的拦截器 去处理这些问题 当然这些你都可以重写

    shiro权限核心配置

    核心: FilterChain 过滤器链
    废话不多说了
    看代码

    image.png
        /**
         * 权限过滤器配置
         *
         * @param securityManager 安全管理器(核心)
         * @param filterChainService 自定义的权限过滤功能
         * @return shiro过滤器工厂
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager,
                                                             @Qualifier("filterChainDefinitionService") FilterChainDefinitionService filterChainService) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            HashMap<String, Filter> filterHashMap = new HashMap<>(10);
            //自定义的login filter
            filterHashMap.put("login", new LoginFilter());
            //自定义的role filter
            filterHashMap.put("role", new RoleFilter());
            shiroFilterFactoryBean.setFilters(filterHashMap);
            //设置权限管理器
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            //导入权限过滤链数据(使用map的形式配置)
            //主要必须为LinkedHashMap 权限是有顺序之分的
            //Map<String, String> map = new LinkedHashMap<>();
            //map.put("/js/**", "anon");
            //map.put("/css/**", "anon");
            //map.put("/open/**", "anon");
            //map.put("/static/**", "anon");
            //map.put("/login", "anon");
            //登出
            //map.put("/logout", "logout");
            //map.put("/**", "user");
            //map形式导入
            // shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    
    
            //导入权限过滤链数据(String导入的形式)
            String definitions = filterChainService.loadFilterChainDefinitions();
            //整个权限过滤链string导入
            shiroFilterFactoryBean.setFilterChainDefinitions(definitions);
            //配置默认登录url
            shiroFilterFactoryBean.setLoginUrl("/login");
            //首页
            shiroFilterFactoryBean.setSuccessUrl("/afterLogin/index.html");
            //错误页面,认证不通过跳转
            shiroFilterFactoryBean.setUnauthorizedUrl("/beforeLogin/error.html");
            return shiroFilterFactoryBean;
        }
    

    首先第一步 肯定就是得告诉 shiro
    哪些url是要拦截的 哪些是要权限拦截的
    首先是 比较适合简单项目 和springsecurity一样的注解式拦截功能 老aop功能了 害
    我觉比较麻烦 每次要加 还是通配符香一点 所以推荐这行不看

     /**
         * 权限注解校验 url比较少的情况可以使用
         *
         * @return
         */
        @RequiresRoles("admin")
        @GetMapping("/admin")
        public String admin() {
            return "admin success!";
        }
    
        /**
         * 权限注解校验 url比较少的情况可以使用
         *
         * @return
         */
        @RequiresPermissions("query")
        @GetMapping("/query")
        public String query() {
            return "query success!";
        }
    
        /**
         * 权限注解校验 url比较少的情况可以使用
         *
         * @return
         */
        @RequiresPermissions("insert")
        @GetMapping("/insert")
        public String insert() {
            return "insert success!";
        }
    

    网上大部分都是这种 map形式


    image.png
            //导入权限过滤链数据(使用map的形式配置)
            //主要必须为LinkedHashMap 权限是有顺序之分的
            Map<String, String> map = new LinkedHashMap<>();
            //你想要的放行的静态资源(触发shiro默认的anon过滤器 其实就是放行)
            //anon=任何人都可以访问
            map.put("/js/**", "anon");
            map.put("/css/**", "anon");
            map.put("/open/**", "anon");
            map.put("/static/**", "anon");
            map.put("/login", "anon");
            //登出
            //触发shiro默认的logout过滤器 有一点小参数 具体可以去源码看 挺简单的
            map.put("/logout", "logout");
            //一般有authc/user两种 都是shiro默认的过滤器
            //user就是多了一个rememberMe记住密码功能
            //authc登陆认证 过滤器 就是shiro的登录的正常拦截 看看你有没有登录呀  是不是登录请求啊 是用自己登录方法 还是我的啊
            //这些过滤器 会在源码那篇文章 读一读的 下次一定qwq
            map.put("/**", "authc");
            map.put("/**", "user");
            //map形式导入
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    

    map最需要注意的就是顺序 所以用有序的linkedhashmap 表示范围包含的关系
    导入进去 shiro就知道哪些url是需要拦截 哪些是要放行的了
    当然 如果项目中有几百个url要设置 怎么办
    当然你可以选择写个类 初始化bean的时候 把数据库的数据封装成一个map 然后再导入进来
    我这边是用的另一个api 以string的方式导入 也是一样的

            //导入权限过滤链数据(String导入的形式)
            String definitions = filterChainService.loadFilterChainDefinitions();
            //整个权限过滤链string导入
            shiroFilterFactoryBean.setFilterChainDefinitions(definitions);
    
    image.png

    在自定义的类里面 进行了拼接
    然后把数据导入到 shiro的 FilterChain权限过滤链

     /**
         * 拼接权限字符串
         *
         * @return 拼接好的权限字符串
         */
        @Override
        public String loadFilterChainDefinitions() {
            StringBuffer sb = new StringBuffer("");
            //默认配置权限
            sb.append(filterChainFromTxt())
                    //数据库配置权限
                    .append(getDbAuthRule())
                    //配置其余权限
                    .append(LAST_AUTH_CHAIN);
            String chain = sb.toString();
            LOG.info("chain:[{}]", chain);
            return chain;
        }
    

    具体导入url拦截信息 也有很多种方式
    因为旧项目里面有些之前设置的url 不是完全在数据库里面
    ps:一些简单的 后期需要过滤的条件 可以直接写在配置里 写到类里 要找类 有点麻烦
    所以filterChainFromTxt写了一点方法 从配置好的文件里面拿数据

      /**
         * 导入配置文件中的权限(txt方式)
         * 一开始没有找到ini导入攻略 就自己写了个导入规则qwq
         *
         * @return 权限控制链字符串
         */
        private String filterChainFromTxt() {
            String fileName = TXT_FILE_NAME;
            StringBuffer sb = new StringBuffer();
            try {
                //读取文件
                String source = ResourceUtil.readUtf8Str(fileName);
                List<String> info = Arrays.asList(StrUtil.split(source, CRLF));
                info.stream()
                        //过滤掉“//”,“#”,还有空行的影响
                        .filter(string -> !StrUtil.startWith(string, FILTER1)
                                && !StrUtil.startWith(string, FILTER2)
                                && StrUtil.isNotBlank(string))
                        //每条数据后面加个回车
                        .map(string -> string + CRLF)
                        .forEach(sb::append);
            } catch (Exception e) {
                LOG.error("加载文件出错。file:[{}]", fileName);
            }
            return sb.toString();
        }
    

    txt文件

    #放行静态资源
    /u/**=anon
    /js/**=anon
    /css/**=anon
    /open/**=anon
    /beforeLogin/**=anon
    
    #在shiroConfig导入
    #可以写入#号注释
    #注意anon不要写成anno 会出问题
    
    /logout=logout
    /login=authc
    
    //需要添加放行url 写在此行以上
    #anon 可以匿名访问
    #user 开启认证 同时启动记住密码功能
    #addPrincipal 自定义拦截器 记住密码登录 设置session
    #/**=anon
    #/**=login,user
    

    还有shiro默认使用的ini文件 api也比较容易 熟悉一下很容易上手

     /**
         * 导入配置文件中的权限(ini方式)
         *
         * @return 权限控制链字符串
         */
        private String FilterChainFromIni() {
            String fileName = INI_FILE_NAME;
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileName);
            Ini ini = new Ini();
            try {
                assert inputStream != null;
                ini.load(inputStream);
            } catch (Exception e) {
                LOG.error("加载文件出错。file:[{}]", fileName);
            }
            String section = "base_auth";
            Set<String> keys = ini.get(section).keySet();
            StringBuffer sb = new StringBuffer();
            for (String key : keys) {
                String value = ini.addSection(section).get(key);
                sb.append(key)
                        .append(" = ")
                        .append(LOGIN_ROLE_FILTER)
                        .append(value)
                        .append(CRLF);
            }
            return sb.toString();
        }
    

    ini文件

    [base_auth]
    /u/** = anon
    /js/** = anon
    /css/** = anon
    /open/** = anon
    /beforeLogin/** = anon
    #
    /logout = anon
    /login = anon
    [roles]
    admin = *
    simple_guy = light_saber:*
    good_guy = good_saber:drive:eagle5
    
    

    初始化之后 shiro里面就会被导入这样的权限链
    ps:这里注意一下 不是用的shiro默认的roles过滤器
    是自定义的role
    shiro的roles 是校验登陆者的角色是否[superManager, 草系, 火系, 水系]是个全满足 显然不好用

    chain:[
    /u/**=anon
    /js/**=anon
    /css/**=anon
    /open/**=anon
    /beforeLogin/**=anon
    /logout=logout
    /login=authc
    /app/list/**=login,role[superManager, 草系, 火系, 水系]
    /app/add/**=login,role[superManager, 草系]//这里表示这个api会经过登陆过滤器 并且经过角色校验 需要管理员或者草系角色 
    /app/update/**=login,role[superManager, 火系]
    /app/delete/**=login,role[superManager, 水系]
    /** = user]
    

    自定义角色过滤器
    roleFilter
    如果想要知道shiro自定义的过滤器怎么写
    直接去看源码就好了 shiro的源码都是比较简单的那种
    我另一篇文章里面 有展示shiro有哪些默认的过滤器 大部分需要实现的功能都有
    依葫芦画瓢就行了 这点也是shiro比较友善的地方

    image.png
    package com.fromZero.zeroShiro.shiro.filter;
    
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.authz.AuthorizationFilter;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    /**
     *  role和permission校验是有点冗余的  role就满足我了 
     *  权限数据以及在导入角色设置过了
     *
     * @author R4441
     * @Desciption: 角色过滤器
     * @Auther: ZhangXueCheng4441
     * @Date:2020/12/2/002 22:32
     */
    public class RoleFilter extends AuthorizationFilter {
        /**
         * 对请求进行权限过滤
         *
         * @param request http请求
         * @param response http响应
         * @param mappedValue url需要的权限信息
         * @return 默认返回false
         * @throws Exception {@link Exception}
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            Subject subject = getSubject(request, response);
            //获取该url 允许通过的角色信息
            String[] rolesArray = (String[]) mappedValue;
            if (rolesArray == null || rolesArray.length == 0) {
                return true;
            }
            //查询用户
            for (String s : rolesArray) {
                //跳转realm 查询用户权限
                if (subject.hasRole(s)) {
                    return true;
                }
            }
            return false;
        }
    }
    
    

    这个过滤器核心当然就是subject.hasRole()


    (以下闲话)
    上次说过 subject就是当前登录用户的灵魂
    登录等等api都是靠这个
    当我们需要看看这个请求的人有些什么权限 当然要去问一下数据库啦 帮我看看这哥们有啥权限
    然后就会到和数据库交互的realm部分了
    这就是shiro设计得比较好的地方 每个部件拆得很散 自己根据想法去组装 然后等部件过时了 就重写一个换个新的
    有没有一种和分布式思想很像的感觉
    每个模块分开 然后随时可以拆解 换新的模块 模块和模块之间用网络连接(rpc框架等)
    shiro也是这样 不过他们是通过subject进行相互联系的 subject理论上应该是放在threadlocal里面 还没看到具体源码


    image.png

    hasRole就会到这边来问下数据 这个登陆的老哥到底有啥权限
    问了这一次 就会进入shiro的缓存(所以debug功能的话 只会进一次这个api 退出之后清除 才会进第二次)
    如果配置了redis 配置里面开启一下 就可以直接进redis里面

    public class MyRealm extends AuthorizingRealm {
    
        @Resource
        private SysUserService sysUserService;
        @Resource
        private SysUserDao sysUserDao;
    
        /**
         * 权限配置类
         *
         * @param principalCollection 登陆者信息
         * @return AuthorizationInfo 权限信息
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取登录用户 todo
            // MyPrincipalCollection myPrincipalCollection =  (MyPrincipalCollection) principalCollection;
            // String userEmail = (String) myPrincipalCollection.getPrimaryPrincipal();
            SysUser user = (SysUser) principalCollection.getPrimaryPrincipal();
    
            //添加角色和权限
            MyAuthorizationInfo myAuthorizationInfo = new MyAuthorizationInfo();
            Set<String> roles = new LinkedHashSet<>();
            //查询该用户信息
            SysUser sysUser = sysUserDao.selectUserPermissionsByEmail(user.getEmail());
            Set<String> permissions = sysUser.getRoles()
                    .stream()
                    //添加角色信息
                    .peek(role -> roles.add(role.getName()))
                    .map(SysRole::getPermissions)
                    //添加权限信息
                    .flatMap(a -> a.stream().map(SysPermission::getName))
                    .collect(Collectors.toSet());
    
            myAuthorizationInfo.setRoles(roles);
            //myAuthorizationInfo.setStringPermissions(permissions);
            return myAuthorizationInfo;
        }
    
    
    image.png

    发现这个用户是否有该权限 拦截器就完成任务啦 鉴权流程也就结束了
    以后每一次鉴权 都是经过拦截器 而不会进去realm了 因为该用户权限已经缓存进去了 查缓存即可 不用和数据库交互

    copy地址

    https://github.com/opop32165455/zeroBeginning.git
    里面的zero_shiro模块
    其他的模块也在龟速进展中
    欢迎copy 我会一直改进我的代码的 有啥问题 issues讨论一哈
    后面权限校验 配置思路 源码学习 都会写进来的啦
    别的我不敢当 但是写注解 那我肯定是最详细的
    有什么问题欢迎辱骂我

    相关文章

      网友评论

          本文标题:SpringBoot整合shiro(三):鉴权流程

          本文链接:https://www.haomeiwen.com/subject/wukcaktx.html