精华内容
下载资源
问答
  • 导出接口如何做权限控制
    千次阅读
    2019-06-06 18:19:18

    权限控制主要分为两块,认证(Authentication)与授权(Authorization)。认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Based Access Control)。RBAC包含为下面四个要素:用户、角色、权限、资源。用户是源头,资源是目标,用户绑定至角色,资源与权限关联,最终将角色与权限关联,就形成了比较完整灵活的权限控制模型。
    资源是最终需要控制的标的物,但是我们在一个业务系统中要将哪些元素作为待控制的资源呢?我将系统中待控制的资源分为三类:

    1. URL访问资源(接口以及网页)
    2. 界面元素资源(增删改查导入导出的按钮,重要的业务数据展示与否等)
    3. 数据资源

    现在业内普遍的实现方案实际上很粗放,就是单纯的“菜单控制”,通过菜单显示与否来达到控制权限的目的。
    我仔细分析过,现在大家做的平台分为To C和To B两种:

    1. To C一般不会有太多的复杂权限控制,甚至大部分连菜单控制都不用,全部都可以访问。
    2. To B一般都不是开放的,只要做好认证关口,能够进入系统的只有内部员工。大部分企业内部的员工互联网知识有限,而且作为内部员工不敢对系统进行破坏性的尝试。

    所以针对现在的情况,考虑成本与产出,大部分设计者也不愿意在权限上进行太多的研发力量。
    菜单和界面元素一般都是由前端编码配合存储数据实现,URL访问资源的控制也有一些框架比如SpringSecurity,Shiro。
    目前我还没有找到过数据权限控制的框架或者方法,所以自己整理了一份。

    数据权限控制原理

    数据权限控制最终的效果是会要求在同一个数据请求方法中,根据不同的权限返回不同的数据集,而且无需并且不能由研发编码控制。这样大家的第一想法应该就是AOP,拦截所有的底层方法,加入过滤条件。这样的方式兼容性较强,但是复杂程度也会更高。我们这套系统中,采用的是利用Mybatis的plugin机制,在底层SQL解析时替换增加过滤条件。
    这样一套控制机制存在很明显的优缺点,首先缺点:

    1. 适用性有限,基于底层的Mybatis。
    2. 方言有限,针对了某种数据库(我们使用Mysql),而且由于需要在底层解析处理条件所以有可能造成不同的数据库不能兼容。当然Redis和NoSQL也无法限制。

    当然,假如你现在就用Mybatis,而且数据库使用的是Mysql,这方面就没有太大影响了。

    接下来说说优点:

    1. 减少了接口数量及接口复杂度。原本针对不同的角色,可能会区分不同的接口或者在接口实现时利用流程控制逻辑来区分不同的条件。有了数据权限控制,代码中只用写基本逻辑,权限过滤由底层机制自动处理。
    2. 提高了数据权限控制的灵活性。例如原本只有主管能查本部门下组织架构/订单数据,现在新增助理角色,能够查询本部门下组织架构,不能查询订单。这样的话普通的写法就需要调整逻辑控制,使用数据权限控制的话,直接修改配置就好。

    数据权限实现

    上一节就提及了实现原理,是基于Mybatis的plugins(查看官方文档)实现。

    MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    ParameterHandler (getParameterObject, setParameters)
    ResultSetHandler (handleResultSets, handleOutputParameters)
    StatementHandler (prepare, parameterize, batch, update, query)

    Mybatis的插件机制目前比较出名的实现应该就是PageHelper项目了,在做这个实现的时候也参考了PageHelper项目的实现方式。所以权限控制插件的类命名为PermissionHelper。
    机制是依托于Mybatis的plugins机制,实际SQL处理的时候基于jsqlparser这个包。
    设计中包含两个类,一个是保存角色与权限的实体类命名为PermissionRule,一个是根据实体变更底层SQL语句的主体方法类PermissionHelper。

    首先来看下PermissionRule的结构:

    public class PermissionRule {
    
        private static final Log log = LogFactory.getLog(PermissionRule.class);
        /**
         * codeName<br>
         * 适用角色列表<br>
         * 格式如: ,RoleA,RoleB,
         */
        private String roles;
        /**
         * codeValue<br>
         * 主实体,多表联合
         * 格式如: ,SystemCode,User,
         */
        private String fromEntity;
        /**
         * codeDesc<br>
         * 过滤表达式字段, <br>
         * <code>{uid}</code>会自动替换为当前用户的userId<br>
         * <code>{me}</code> main entity 主实体名称
         * <code>{me.a}</code> main entity alias 主实体别名
         * 格式如:
         * <ul>
         * <li>userId = {uid}</li>
         * <li>(userId = {uid} AND authType > 3)</li>
         * <li>((userId = {uid} AND authType) > 3 OR (dept in (select dept from depts where manager.id = {uid})))</li>
         * </ul>
         */
        private String exps;
    
        /**
         * codeShowName<br>
         * 规则说明
         */
        private String ruleComment;
    
    }
    

    看完这个结构,基本能够理解设计的思路了。数据结构中保存如下几个字段:

    • 角色列表:需要使用此规则的角色,可以多个,使用英文逗号隔开。
    • 实体列表:对应的规则应用的实体(这里指的是表结构中的表名,可能你的实体是驼峰而数据库是蛇形,所以这里要放蛇形那个),可以多个,使用英文逗号隔开。
    • 表达式:表达式就是数据权限控制的核心了。简单的说这里的表达式就是一段SQL语句,其中设置了一些可替换值,底层会用对应运行时的变量替换对应内容,从而达到增加条件的效果。
    • 规则说明:单纯的一个说明字段。

    核心流程
    系统启动时,首先从数据库加载出所有的规则。底层利用插件机制来拦截所有的查询语句,进入查询拦截方法后,首先根据当前用户的权限列表筛选出PermissionRule列表,然后循环列表中的规则,对语句中符合实体列表的表进行条件增加,最终生成处理后的SQL语句,退出拦截器,Mybatis执行处理后SQL并返回结果。

    讲完PermissionRule,再来看看PermissionHelper,首先是头:

    @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
            @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    public class PermissionHelper implements Interceptor {
    }
    

    头部只是标准的Mybatis拦截器写法,注解中的Signature决定了你的代码对哪些方法拦截,update实际上针对**修改(Update)、删除(Delete)生效,query是对查询(Select)**生效。

    下面给出针对Select注入查询条件限制的完整代码:

    
        private String processSelectSql(String sql, List<PermissionRule> rules, UserDefaultZimpl principal) {
            try {
                String replaceSql = null;
                Select select = (Select) CCJSqlParserUtil.parse(sql);
                PlainSelect selectBody = (PlainSelect) select.getSelectBody();
                String mainTable = null;
                if (selectBody.getFromItem() instanceof Table) {
                    mainTable = ((Table) selectBody.getFromItem()).getName().replace("`", "");
                } else if (selectBody.getFromItem() instanceof SubSelect) {
                    replaceSql = processSelectSql(((SubSelect) selectBody.getFromItem()).getSelectBody().toString(), rules, principal);
                }
                if (!ValidUtil.isEmpty(replaceSql)) {
                    sql = sql.replace(((SubSelect) selectBody.getFromItem()).getSelectBody().toString(), replaceSql);
                }
                String mainTableAlias = mainTable;
                try {
                    mainTableAlias = selectBody.getFromItem().getAlias().getName();
                } catch (Exception e) {
                    log.debug("当前sql中, " + mainTable + " 没有设置别名");
                }
    
    
                String condExpr = null;
                PermissionRule realRuls = null;
                for (PermissionRule rule :
                        rules) {
                    for (Object roleStr :
                            principal.getRoles()) {
                        if (rule.getRoles().indexOf("," + roleStr + ",") != -1) {
                            if (rule.getFromEntity().indexOf("," + mainTable + ",") != -1) {
                                // 若主表匹配规则主体,则直接使用本规则
                                realRuls = rule;
    
                                condExpr = rule.getExps().replace("{uid}", UserDefaultUtil.getUserId().toString()).replace("{bid}", UserDefaultUtil.getBusinessId().toString()).replace("{me}", mainTable).replace("{me.a}", mainTableAlias);
                                if (selectBody.getWhere() == null) {
                                    selectBody.setWhere(CCJSqlParserUtil.parseCondExpression(condExpr));
                                } else {
                                    AndExpression and = new AndExpression(selectBody.getWhere(), CCJSqlParserUtil.parseCondExpression(condExpr));
                                    selectBody.setWhere(and);
                                }
                            }
    
                            try {
                                String joinTable = null;
                                String joinTableAlias = null;
                                for (Join j :
                                        selectBody.getJoins()) {
                                    if (rule.getFromEntity().indexOf("," + ((Table) j.getRightItem()).getName() + ",") != -1) {
                                        // 当主表不能匹配时,匹配所有join,使用符合条件的第一个表的规则。
                                        realRuls = rule;
                                        joinTable = ((Table) j.getRightItem()).getName();
                                        joinTableAlias = j.getRightItem().getAlias().getName();
    
                                        condExpr = rule.getExps().replace("{uid}", UserDefaultUtil.getUserId().toString()).replace("{bid}", UserDefaultUtil.getBusinessId().toString()).replace("{me}", joinTable).replace("{me.a}", joinTableAlias);
                                        if (j.getOnExpression() == null) {
                                            j.setOnExpression(CCJSqlParserUtil.parseCondExpression(condExpr));
                                        } else {
                                            AndExpression and = new AndExpression(j.getOnExpression(), CCJSqlParserUtil.parseCondExpression(condExpr));
                                            j.setOnExpression(and);
                                        }
                                    }
                                }
                            } catch (Exception e) {
                                log.debug("当前sql没有join的部分!");
                            }
                        }
                    }
                }
                if (realRuls == null) return sql; // 没有合适规则直接退出。
    
                if (sql.indexOf("limit ?,?") != -1 && select.toString().indexOf("LIMIT ? OFFSET ?") != -1) {
                    sql = select.toString().replace("LIMIT ? OFFSET ?", "limit ?,?");
                } else {
                    sql = select.toString();
                }
    
            } catch (JSQLParserException e) {
                log.error("change sql error .", e);
            }
            return sql;
        }
    

    重点思路
    重点其实就在于Sql的解析和条件注入,使用开源项目JSqlParser

    • 解析出MainTable和JoinTable。from之后跟着的称为MainTable,join之后跟着的称为JoinTable。这两个就是我们PermissionRule需要匹配的表名,PermissionRule::fromEntity字段。
    • 解析出MainTable的where和JoinTable的on后面的条件。使用and连接原本的条件和待注入的条件,PermissionRule::exps字段。
    • 使用当前登录的用户信息(放在缓存中),替换条件表达式中的值。
    • 某些情况需要忽略权限,可以考虑使用ThreadLocal(单机)/Redis(集群)来控制。

    结束语

    想要达到无感知的数据权限控制,只有机制控制这么一条路。本文选择的是通过底层拦截Sql语句,并且针对对应表注入条件语句这么一种做法。应该是非常经济的做法,只是基于文本处理,不会给系统带来太大的负担,而且能够达到理想中的效果。大家也可以提出其他的见解和思路。

    更多相关内容
  • 最近在一个小项目的开入API,文档是用vuepress写的静态md并发布。因为是项目刚起步,对接的接口也会经常改动,每次改动都要修改文档感觉太麻烦,就想到用knife4j来代替。但外部使用的接口和内部使用不一样,需要...

    一、原由

    最近在做一个小项目的开入API,文档是用vuepress写的静态md并发布。因为是项目刚起步,对接的接口也会经常改动,每次改动都要修改文档感觉太麻烦,就想到用knife4j来代替。但外部使用的接口和内部使用不一样,需要一些接入引导说明,比如API接入步骤、鉴权说明…等

    重新翻了knife4j的文档发现2.x版本自2.0.7之后可以使用自定义文档的功能,自定义文档正好也是我喜欢的Markdown文件,再加一个权限验证,目前是很好的解决了我的问题。

    效果:
    在这里插入图片描述

    二、开始

    knife4j2.x版如果想实现自定义Markdown文档需要将版本升级2.0.7及以上

    本次测试使用的springboot版本 2.0.3.RELEASE

    需要用到的依赖

    //swagger版本支持
    compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.10.5'
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.10.5'
    compile 'io.springfox:springfox-spring-webmvc:2.10.5'
    //swagger注解无默认值错误忽略
    compile group: 'io.swagger', name: 'swagger-annotations', version: '1.5.22'
    compile group: 'io.swagger', name: 'swagger-models', version: '1.5.22'
    //knife2.x版本
    compile group: 'com.github.xiaoymin', name: 'knife4j-spring-boot-starter', version: '2.0.8'
    //升级spring-plugin-core,否则会有冲突
    compile 'org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE'
    

    SwaggerConfiguration

    package com.only.oc.openapi.config;
    
    import com.fasterxml.classmate.TypeResolver;
    import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
    
    
    @Configuration
    @EnableSwagger2WebMvc
    @Import(BeanValidatorPluginsConfiguration.class)
    public class SwaggerConfiguration {
    
        private final TypeResolver typeResolver;
        private final OpenApiExtensionResolver openApiExtensionResolver;
    
        @Autowired
        public SwaggerConfiguration(TypeResolver typeResolver, OpenApiExtensionResolver openApiExtensionResolver) {
            this.typeResolver = typeResolver;
            this.openApiExtensionResolver = openApiExtensionResolver;
        }
    
    
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.only.oc.openapi.api"))//扫描包
                    .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))//扫描在API注解的contorller
                    .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))//扫描带ApiOperation注解的方法
                    .paths(PathSelectors.any())
                    .build()
                    .extensions(openApiExtensionResolver.buildExtensions("2.X版本"));
        }
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("昂立课堂(开放API)")
                    .description("昂立课堂开放平台")
                    .version("v2.x")
                    .license("Apache License Version 2.0")
                    .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0")
                    .contact(new Contact("作者", "", "xxx@qq.com"))
                    .build();
        }
    
    }
    
    

    application.yml

    server:
      port: 8080
    
    knife4j:
      enable: true
      # 生产环境屏蔽
      # production: true
      # 开启Swagger的Basic认证功能,默认是false
      basic:
        enable: true
        username: only
        password: only
      documents:
        -
          group: 2.X版本
          name: 另外文档分组请看这里
          locations: classpath:markdown/*
        - group: 2.X版本
          name: 接入方法
          locations: classpath:markdown/api.md
    

    文档结构位置
    在这里插入图片描述

    源码

    https://codechina.csdn.net/leopast/knife4j-demo.git

    三、坑

    当你一切准备就绪时就报如下错误
    在这里插入图片描述
    这里需要升级spring核心插件

    compile 'org.springframework.plugin:spring-plugin-core:2.0.0.RELEASE'
    

    原创文章未经本人许可,不得用于商业用途及传统媒体。转载请注明出处,否则属于侵权行为,谢谢合作!

    展开全文
  • Vue 项目 菜单权限及按钮级权限控制

    千次阅读 2020-06-24 12:05:50
    3.全局自定义指令(directive)控制按钮权限数据的方法,登入时获取后端传来的按钮权限数组。 4.在每个按钮中调用该指令,并传入该操作的权限字段和后端保存的权限字段进行匹配,能匹配则该操作按钮可显示 具体代码...

    实现思路

    1.页面展示需要鉴权的所有按钮,需要先鉴权菜单权限的显示与隐藏。

    2.勾选每个角色或者用户所能看的权限保存在数据库。该权限数据是一个权限字段的数组。

    3.全局自定义指令(directive)控制按钮权限数据的方法,登入时获取后端传来的按钮权限数组。

    4.在每个按钮中调用该指令,并传入该操作的权限字段和后端保存的权限字段进行匹配,能匹配则该操作按钮可显示

    具体代码如下

    1. 获取登录用户所有权限
    // 获取权限菜单(存在store下modules下user.js的actions中)
     getMenuTree ({ commit, state }) {
       return new Promise((resolve, reject) => {
         getMenuTree(state.token).then(response => {
           const data = response.result
           if (!data) {
             reject('验证失败,请重新登录')
           }
           // 存菜单结构
           commit('SET_ROLES', data)
           // 重置按钮权限
           btns = []
           const btn = findAllBtn(data)
           // 存所有按钮权限
           commit('SET_BUTTONS', btn)
           resolve(data)
         }).catch(error => {
           reject(error)
         })
       })
     }
    // 递归获取按钮list
    let btns = []
    export function findAllBtn (list) {
     list.forEach(val => {
     // 与后台协商所有菜单资源(resCode)下的type是1表菜单,2为按钮
       if (val.type === '1') {
         if (val.children && val.children.length > 0) {
           findAllBtn(val.children)
         }
       } else {
         btns.push(val.reCode)
       }
     })
    }
    
    1. 对比菜单权限的方法
    在store下modules下新建一个permission.js(获取最终动态权限菜单)
    /**
    * @param  {Array} userRouter 后台返回的用户权限json
    * @param  {Array} allRouter  前端配置好的所有动态路由的集合
    * @return {Array} realRoutes 过滤后的路由
    */
    export function recursionRouter (userRouter = [], allRouter = []) {
     let realRoutes = []
     allRouter.forEach(val => {
       userRouter.forEach(item => {
         if (val.path.includes('/')) {
           if (item.resCode === val.path.split('/')[1]) {
             val.children = recursionRouter(item.children, val.children)
             realRoutes.push(val)
           }
         } else {
           if (item.resCode === val.path) {
             if (item.children && item.children.length > 0) {
               val.children = recursionRouter(item.children, val.children)
             }
             realRoutes.push(val)
           }
         }
       })
     })
     realRoutes.push({ path: '*', redirect: '/404', isShow: true })
     // console.log(222, realRoutes)
     return realRoutes
    }
    // asyncRouterMap本地配置好的所有动态路由的集合
    const actions = {
     generateRoutes ({ commit }, roles) {
       return new Promise(resolve => {
         let accessedRoutes = recursionRouter(roles, [...asyncRouterMap])
         commit('SET_ROUTES', accessedRoutes)
         resolve(accessedRoutes)
       })
     }
    }
    

    对比菜单权限需要在全局路由守卫中如下操作

    router.beforeEach(async (to, from, next) => {
     // 确定用户是否已登录
     const hasToken = getToken()
     // 判断是否有token
     if (hasToken) {
       // 运营端登录
       if (to.path === '/login') {
         // 如果已登录,则重定向到主页
         next({ path: '/' })
         NProgress.done()
       } else {
         // 确定用户是否已获得了他的权限角色
         const hasRoles = store.getters.roles && store.getters.roles.length > 0
         if (hasRoles) {
           next()
         } else {
           // 角色必须是对象数组
           const roles = await store.dispatch('user/getMenuTree')
             // 根据角色生成可访问路由映射
             const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
             // 清空静态路由
             resetRouter()
             // 动态添加可访问路由
             router.addRoutes(accessRoutes)
             // hack 方法,以确保addRoutes是完整的
             // 设置replace: true,这样导航就不会留下历史记录
             next({ ...to, replace: true })
         }
       }
     } else {
       // 在免登录白名单,直接进入
       if (whiteList.indexOf(to.path) !== -1) {
         next()
       } else {
         // 没有访问权限的其他页面被重定向到登录页面
         next(`/login?redirect=${to.path}`)
         NProgress.done()
       }
     }
    })
    
    1. 全局自定义指令
    // 需要全局注入(即在main.js引入)
    import Vue from 'vue'
    import store from '@/store'
    /**
     * 使用:v-permission="'resCode'"
     *   resCode 按钮资源(即路由path)
     * **/
    Vue.directive('permission', {
      inserted (el, vDir) {
        let btnPermission = store.getters.buttons
        if (vDir.value) {
          if (!btnPermission.includes(vDir.value)) {
            el.parentNode.removeChild(el)
          }
        }
      }
    })
    
    1. 页面中使用
    <el-button type="primary" @click="roleExport" v-permission="'ent-role-export'">导出</el-button>
    

    如图

    初始在这里插入图片描述
    取消权限后
    在这里插入图片描述
    在这里插入图片描述


    相关文章

    基于elementUI中table组件二次封装(Vue项目)


    axios二次封装,接口统一存放,满足RESTful风格


    keep-alive不能缓存多层级路由(vue-router)菜单问题解决


    基于ElementUi再次封装基础组件文档

    展开全文
  • 基于Vue的addRoutes实现权限控制

    万次阅读 热门讨论 2018-05-15 15:25:57
    最近老师布置了基于Vue的权限控制,网上查阅了许多博客,例如“前端路上":这篇文章提到了Vue2.0以上使用的addRoutes增加动态路由的思路。然而我只是一个小白,尽管文章里贴出了源码,我也只能看的一知半解,...

    前言

    从今年3月到现在入门前端也已经有两个月了,这两个月在自学的过程中遇到了许多问题也解决了其中一部分,但是现在才开始意识到把学习过程的问题整理到CSDN上。最近老师布置了基于Vue的权限控制,网上查阅了许多博客,例如“前端路上":这篇文章提到了Vue2.0以上使用的addRoutes增加动态路由的思路。然而我只是一个小白,尽管文章里贴出了源码,我也只能看的一知半解,所以根据文章中提供的思路自己写了一个小测试demo。

    目录结构

    准备工作

    在路由配置文件里写上两个路由表,一个静态路由表(所有权限用户均能访问如“登录”、“404页”)和一个动态路由表,默认导出静态路由。

    const router = new Router({ //静态路由,登录、404
      mode:'history',
      routes: [
        {
          path: '/login', //登录页
          name: 'login',
          component: (resolve) => require(['../components/login.vue'], resolve)
        },
        {
          path: '/404',  //404页
          name: 'err',
          component: (resolve) => require(['../components/err.vue'], resolve)
        },
      ]
    });
    export default router //默认静态路由
    export const dynamicRouter =[ //动态路由
      {
        path: '/',
        name: 'home',
        meta: {  //添加meta标签,里面自定义了roles,用来设置权限
          roles:['admin','user']  //该路由仅admin和user权限访问
        },
        component: (resolve) => require(['../components/home.vue'], resolve)
      },
      {
        path: '/',
        name: 'home2',
        meta: {
          roles:['tourist'] //该路由仅tourist权限访问
        },
        component: (resolve) => require(['../components/home2.vue'], resolve)
      },
      {
        path: '/home21',
        name: 'home21',
        meta: {
          roles:['user']
        },
        component: (resolve) => require(['../components/home21.vue'], resolve)
      },
      {
        path: '/home11',
        name: 'home11',
        meta: {
          roles:['admin']
        },
        component: (resolve) => require(['../components/home11.vue'], resolve)
      },
    ];

    这里我们已经配置好了路由表。

    思路

    如果用户没有登录,那就将页面重定向到“/login”,实现方法:在跟组件(App.vue)的created回调里做判断

    methods:{
        judgelogin(){ //methods写一个判断是否登录的方法
          let isLogin = store.state.token; //这里我们从Vuex里取出token,因为登录请求成功后我们会将后台返回的token存储在vuex里方便使用
          if(!isLogin){ //如果没有token,那就代表用户没有登录
            return this.$router.push('/login'); //重定向到login页
          } 
        }
      },
    created() {
         this.judgelogin(); //在created里调用上面构造的方法
      }

    登录页:

    HTML:
    template>
      <div id="login">
        <form>
          <label>账号</label><input type="text" v-model="username"/><br>
          <label>密码</label><input type="password" v-model="password"/><br>
          <input type="button" value="登录" @click="Login">
        </form>
      </div>
    </template>
    JS:
    import {router,dynamicRouter} from '../router/index' //获取路由配置文件里的两个路由表
    
    export default {
      name: 'login',
      data () {
        return {
          username:'', //双向绑定获取账号
          password:'', //双向绑定获取密码
        }
      },
      methods:{
        Login(){
          sessionStorage.setItem('token',this.username); //将token和roles保存下来,因为没有后台接口,我这边就将账号当做token和roles
          sessionStorage.setItem('roles',this.username);
          let dR = new Array(); //创建一个数组用来存储符合权限的路由
          for (let i=0;i<dynamicRouter.length;i++){ //第一层循环遍历动态路由表的每一个路由
            for (let j=0;j<dynamicRouter[i].meta.roles.length;j++){ //第二次循环遍历每一个路由里的roles设置的权限并和当前登录账号的权限比较
              if (dynamicRouter[i].meta.roles[j] == this.username){ //这里因为我默认账号名就是当前用户的权限
               dR.push(dynamicRouter[i]); //符合条件的路由信息就放进数组里
             }
            }
          }
          this.$router.addRoutes(dR.concat([{ //这里调用addRoutes方法,动态添加符合条件的路由
            path: '*',
            redirect: '/404' //所有不匹配路径(*)都重定向到404,为什么写在这里而不放到静态路由表里可以查看“前端路上”的文章
          }]));
          this.$router.push('/'); //登录验证后跳转到主页"/"
        }
      },
      created(){
        sessionStorage.clear(); 
      }
    }

    到这里我们用账号“admin”登录后就可以成功跳转到主页并显示"home.vue"的内容,我们也可以修改url访问"/home11"。但是当我们访问"/home2"和"/home21"的时候没有出现我们预期的跳转到"/404"页,这里出了一个bug不知道为什么,希望用大神能够指正出来,小弟感激不尽!

    前方有坑:

    登陆后,当我们按"F5"键刷新页面后,页面内容都消失不见了(理论上应该是/404页内容)。原来addRoutes和Vuex一样都是刷新后数据会清空(这个地方我感觉是真的坑)。这是因为刷新会导致Vue重新实例化,路由也恢复到了初始路由,也就是前面的静态路由表。

    改进:

    在根组件的created回调里,我们不仅得判断用户是未登录状态还是登陆后又刷新,改进代码如下

    judgeLogin(){
          let isLogin = store.state.token;
          if(!isLogin){
            return this.$router.push('/login');
          } else {
            let dR = new Array();
            for (let i=0;i<dynamicRouter.length;i++){
              for (let j=0;j<dynamicRouter[i].meta.roles.length;j++){
                if (dynamicRouter[i].meta.roles[j] == store.state.roles){
                  dR.push(dynamicRouter[i]);
                }
              }
            }
            this.$router.addRoutes(dR);
          }
        }

    这样即使刷新,你生成的动态路由也不会失效了。

    附上:

    因为是小测试,所以Vuex里只存了两个数据:

    export default new Vuex.Store({
      state:{
        token:sessionStorage.getItem('token'), 
        roles:sessionStorage.getItem('roles'),
      },
    })

    结:

    第一次写博客还是有点激动的,不知道这个权限控制思路能不能对大家有所帮助。这样的权限控制其实分不同权限写了几套页面,然后根据权限动态添加符合权限的页面。其实我一开始的设想是一个主页的菜单栏根据不同权限的用户显示不同的导航(这个实现效果看“前端路上”大能分享的github上源码),但是技术有限还未能实现,后续实现后会贴出来的。


    展开全文
  • 本篇文章为本人记录笔记,如果有需要,请查看原始文章:https://blog.csdn.net/m0_38013911/article/details/108271607 ...由于LayaAir引擎当前是依托于Unity工具编辑3D场景并导出资源的,那Unity中到底是哪些功能可.
  • 前后端分离中的权限管理思路

    千次阅读 2022-01-16 15:05:29
    前端是展示给用户看的,所有的菜单显示或者隐藏目的不是为了实现权限管理,而是为了给用户一个良好的体验,不能依赖前端隐藏控件来实现权限管理,即数据安全不能依靠前端,就像普通的表单提交一样,前端数据校验是...
  • 数据导出流程设计

    千次阅读 2022-04-12 23:37:53
    导出流程设计
  • 接口权限控制 页面级访问权限控制 创建路由表 Mock权限列表数据 编写导航钩子 数据级操作权限控制 路由控制完整流程图 NEXT——登录及系统菜单加载 源码 本系列目录 前端权限控制的本质 在管理系统中,...
  • 我们知道帆软报表的决策系统里有权限管理菜单, 这里面首先分为两大类: 普通权限配置:就是将目录权限,人员管理,管理系统中的功能授权到部门,角色,或者具体的人身上。 授权权限配置:就是某个人他...
  • webservice权限控制

    2017-10-18 14:30:00
    webservice权限控制设计及说明 已有2.0版本,自动实现接口,取消了eclipse生成实现类的过程,详情见git上2.0文档 代码地址(1.0) https://coding.net/u/mich/p/easytry/git/tree/master/WebService 代码地址...
  • ),现在找工作好多公司都要求有接口测试经验,也有好多人问我(也就两三个人)什么是接口测试,本着不懂也要装懂的态度,我会说:所谓接口测试就是通过测试不同情况下的入参与之相应的出参信息来判断接口是否符合或...
  • 微服务权限校验方案欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居...
  • 基于mysql 的 python学生信息管理系统(附带访问控制、数据导入导出、统计和可视化) 使用 python 自带的 tkinter 库实现基于 mysql 的简单图形化学生信息管理系统。在本地安装好 mysql 即可打开使用。功能有: 简单...
  • 本篇文章试图讲解若依Vue系统中的权限设计原理以及实战,为什么是“试图”?因为这也是摸索着理解的,不一定准 若依Vue系统中的权限管理部分的功能...接口权限:用户带着认证信息请求后端接口,是否有权限访问,该接口
  • Shiro权限管理详解

    千次阅读 2022-03-28 13:24:59
    基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。 权限管理包括用户身份...
  • ftp服务器 文件权限设置

    千次阅读 2021-08-11 02:51:13
    ftp服务器 文件权限设置 内容精选换一换用户在FusionInsight Manager界面配置监控指标转储后,系统按转储时间间隔(默认60秒)周期性检测监控指标转储结果,转储失败时产生该告警。转储成功后,告警恢复。监控指标转储...
  • easyExcel关于导入导出的用法及校验

    千次阅读 2021-12-28 10:22:39
    easyExcel关于导入导出的用法及校验
  • 后台系统设计——角色权限

    千次阅读 2021-02-19 19:48:53
    多数情况下,除了超级管理员外,我们会对大多数的账号的权限做一些限制,以此来管理不同用户的使用权限问题。 譬如,企业使用类软件,不同部门、不同职位的人的权限是不同的;再例如一款收费产品的收费用户和免费...
  • 但是一些系统进程ANR,就搞得手机一时半会没法用,手机就感觉脱离了我们的控制,真的是很悲伤啊。 原因如此,那平时开发如何避免呢 1.主线程中不要任何耗时操作,将这些放到子线程去 2.开发中注意线程...
  • 在前端的api接口中,加上 headers : { isToken : false } 第二种 // 使用 permitAll() 方法所有人都能访问,包括带上 token 访问 .antMatchers("/admins/**").permitAll() // 使用 anonymous() 所有人都能访问,...
  • 1、vue页面的实现 1.1 在工具栏增加显示/隐藏列操作按钮(带权限控制) 找到页面上的right-toolbar标签,具体内容如下: 修改right-toolbar标签的内容如下,增加了:columns属性及v-hasPermi属性。 queryTable=...
  • 常见的API接口管理工具

    万次阅读 多人点赞 2018-11-03 17:34:35
    1、Postman ...幸运的是,Postman可以将调试过的请求保存到Collection中。形成的Collection就可以作为一份简单有效且...对QA来说,使用Postman进行接口测试和接口文档维护是同一件事情,测试即文档,维护成本也很低...
  • 所有的功能都是基于上面的模型表构建的,目前资源权限控制只做到了菜单url,等到后期有空,希望能精确到具体的字段,把功能的更加完善. 核心功能的实现 RBAC10张数据表(用户表,用户组表,角色表,权限表,菜单表,...
  • 首先,什么是接口呢? 接口一般来说有两种,一种是程序内部的接口,一种是系统对外的接口。 系统对外的接口:比如你要从别的网站或服务器上获取资源或信息,别人肯定不会把数据库共享给你,他只能给你提供一个他们写...
  • 接口一般来说有两种,一种是程序内部的接口,一种是系统对外的接口
  • 接口测试及常用接口测试工具

    千次阅读 2022-01-13 21:26:02
    首先,什么是接口呢? 接口一般来说有两种,一种是程序内部的接口,一种是系统对外的接口。 系统对外的接口:比如你要从别的网站或服务器上获取资源或信息,别人肯定不会把数据库共享给你,他只能给你提供一个他们写...
  • 通用数据权限设计方案

    千次阅读 2021-08-10 13:48:09
    近期在数据中台的项目,主要包括元数据管理,质量规则,数据服务,数据导出等多个业务模块,目前该中台只是供内部使用,但随着业务的正式上线,必然会在公网进行访问。因此,如何控制每个用户只能访问自己能够看到...
  • 访问ftp服务器文件夹权限设置

    千次阅读 2021-08-11 01:02:35
    访问ftp服务器文件夹权限设置 内容精选换一换该任务指导用户使用Loader将数据从HBase导出到SFTP服务器。创建或获取该任务中创建Loader作业的业务用户和密码。确保用户已授权访问作业执行时操作的HBase表或phoenix表...
  • 导出数据为PDF方法

    千次阅读 2019-08-11 20:32:27
    有很多方法可以PDF的导出,JAVA就有很好的组件可以这个事情。可以根据项目不同的选择,下面我们简单探讨一下。 常用的导出方法对比调研对 常用PDF导出方法 调研对象 优点 缺点 分页 图片 ...
  • 即import路由组件 src/views/import/index.vue 在页面中使用前面封装的excel上传组件,并补充导入成功后的回调函数 ​ 复制代码 配置路由 这个路由不需要根据权限控制,直接定义为静态路由即可。在**src/router/...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,933
精华内容 13,573
热门标签
关键字:

导出接口如何做权限控制