🌞前端常见权限处理浅析(web端/小程序端)

921 阅读7分钟

前端权限处理,说难不难,说易不易。但是理清思路的话就豁然开朗--就是不同的用户具有不同的权限,然后根据权限限制用户访问页面和点击按钮

前置知识

用户并不直接对应权限,用户对应的其实是角色

也就是说:用户对应角色,角色对应权限。如果我们有一百个用户,但是他们等级不同,老板有全部的权限,经理有80%权限,组长有50%权限,职员有20%权限。

那么其实我们只需要4个角色:

  • 老板角色 有全部的权限,
  • 经理角色 有80%权限,
  • 组长角色 有50%权限,
  • 职员角色 20%权限。

用户只要对应这4个角色即可,不同级别的用户就能有不同的权限了。

接口权限

调用权限验证接口没有通过时,后端一般返回401 Authentication Required, 前端则返回登录页重新登录

登录完拿到Token,将Token存起来(cookie/localStorage),每次调用业务接口时,请求头需要携带Token

401

用户没有权限(令牌、用户名、密码错误),主要情景包括但不限于以下

  • 登录失败-用户名、密码错误
  • Token过期

403

用户得到授权,但是访问是被禁止的

  • 登录成功,并且Token有效, 但是此条接口没有授予访问权限

登录权限逻辑

所谓登录权限,就是用户输入账密码后,点击登录按钮(调用登录API)能否正常登录系统。

1. 调用登录接口

登录成功后,接口可能返回的数据类型主要有以下情景

  1. 情景二: 返回用户信息(包括 Token, 菜单-可以访问的页面 姓名,电话.....)
  2. 情景二: 实际开发中一般是这个场景
    • 2.1. 返回用户信息(包括 Token, 姓名,电话.....)
    • 2.2. 调用获取用户信息的接口
    • 2.3. 调用获取菜单的接口,返回当前用户可以访问的菜单列表

如果调用登录接口成功,则将Token存储到 cookie/localStorage 中; 如果调用登录接口失败,则只在登录界面出现错误提示(不做其他多余动作)

2. 调用获取用户信息的接口

返回用户信息( 等更详细的用户信息.....),将数据存储到全局(如Redux)中,供其他业务界面使用

3. 调用获取菜单的接口

现代前端基本都是单页面应用开发,如Vue,React,都是通过一份路由文件来对系统菜单及页面进行配置。

注:菜单-路由-页面 虽然界面展现形式不同,但都可以理解为同一个意思

  1. 前端项目一般都会有一个菜单配置文件(包含了此系统所有的页面菜单) 菜单A
  2. 调用菜单接口成功后,返回了菜单(包含了当前用户可以访问的页面菜单),**菜单B **;
    • 2.1. 如果 菜单B 返回数据为空,则跳转到 403界面
    • 2.2. 如果 菜单B 有数据,则执行下面的步骤
  3. 菜单A数据与 菜单B 数据对比(主要逻辑是进行双重循环), 如果存在当前用户无权访问的菜单数据,则新加一个 类似authority=false(无权限)的字段,表示该页面没有权限;
  4. 处理后得到最新的一份菜单数据-菜单C,取出此数据中 第一个 authority=true(有权限) 的数据,并跳转到这个界面

以上,整个登录逻辑大致如此

页面访问权限

菜单可以动态。路由一定是静态的,一定是提前写死的,不管是路由配置还是约定式。他都是要穷尽你项目中的可访问路由的。然后菜单才是可选的,比如你只给A用户显示三个路由菜单。然后在菜单权限这里判断,用户当前访问路由,不在这个权限列表中时,跳转到403页面。

逻辑流程图

在路由拦截中判断当前用户是否具有对应的页面路由权限

页面访问权限逻辑流程图

页面鉴权逻辑处理中

页面鉴权逻辑处理中,应该出现 loading页面

loading页面

403页面

一般情况, 用户登录成功后界面返回了指定的菜单, 某些菜单/页面没有权限,则应该跳转到 403页面

403页面

404页面

当用户在浏览器中输入 系统中不存在路由地址,则应该跳转到 404页面

404页面

按钮权限

举个栗子:

  • 用户A进入页面,用户A对应的角色是职员,所有职员角色进入页面,页面内都会有增加查找两个选项;

  • 用户B进入页面,用户B对应的角色是组长,所有组长角色进入页面,页面内都会页面内有增加,查找,修改,删除四个选项;

  • 用户C进入页面,用户C没有对应的角色,没有对应角色的用户进入页面,页面都会提示权限不足,3秒后将跳转首页;

思路

一个页面会有新增,删除,编辑等等按钮。不同用户应该是有不同操作权限的。我们不妨定义权限码

  • 0:不可见
  • 1:不可编辑
  • 2:可编辑

我们提前和后端约定好按钮的名字,后端会返回一个按钮权限列表。获取此列表的时机可以分为以下几种

  1. 在获取用户信息的接口中,返回所有页面及其按钮权限的映射关系,存储到 全局(如Redux)中
  2. 路由拦截: 用户访问某一个具体页面的时候,再重新获取 当前页面的按钮权限

然后我们根据权限列表给按钮绑定disabled属性达到相应权限效果。实质就是根据相应权限操作DOM

二 .移动端权限验证

与web端逻辑类似

react-native 权限验证 

进入app首先会进入一个权限验证页面,此页面可以是一个欢迎页,在此欢迎页中从Storage中取Token

  • 如果没有Token则到登录界面;
  • 如果有Token,则根据Token获取当前用户信息;
    • 如果接口返回登录失效,则到登录界面
    • 如果接口返回成功则跳转到业务界面首页

小程序登陆鉴权流程

小程序登陆鉴权流程

上面是小程序官方文档的登陆鉴权流程图

登陆方式一(需要手机号,用户名称等信息...)

  1. 在登录页面的 onLoad 函数中
    • 1.1 调取 wx.login 获取 code 字段
    • 1.2 调用后端登陆接口(需要传递的参数为 {code:string}),即可返回openid,sessionid
  /**
   * 生命周期函数--监听页面加载
   */
  async onLoad(options) {
    try {
    // 判断Token是否存在,若存在在跳转代首页
      var value = wx.getStorageSync('Authorization')
      if (value&&value.length>0) {
        wx.redirectTo({
          url: '/pages/index/index',
        })
        return;
      }
    } catch (e) {
      // Do something when catch error
    }

    // 登录 -发送 code 到后台换取 openId, sessionKey, unionId
    wx.login({
      success: async res => {
        if (res.code) {
          const result = await loginAuthService({ code: res.code })
          if(!(result&&result.loginToken)){
            this.setData({ loading: false })
            return;
          }
          // 将loginToken(登录接口需要的Token) 及用户信息存储到全局  
          app.globalData.authInfo = {
            ...app.globalData.authInfo,
            ...result
          }
          this.setData({
            loading: false
          })

        } else {
          wx.showToast({
            title: '登录失败',
            icon: 'fail',
            duration: 1000
          })
          this.setData({ loading: false })
        }
      }
    })
  },

  1. 点击登录按钮
    • 2.1. 获取手机号的加密信息及其他需要微信授权的信息
    • 2.2. 调用后端解密手机号的接口,即可获得真实的手机号及其他用户相关信息
    • 2.3. 跳转到首页
// 点击登录按钮, 获取手机号等信息
  async getPhoneNumber(e) {
    if (e.detail.errMsg.includes('fail')) {
      return;
    }

    try {
       // 调取后端接口进行真正的登录权限验证
      const res = await loginService({
        phoneNumber: {
          encryptedData: e.detail.encryptedData,
          iv: e.detail.iv,
        },
        // loginToken(登录接口需要的Token)
        loginToken: app.globalData.authInfo.loginToken,
      });

      if (res&&res.tokenInfo) {
        const { tokenInfo } = res;
        const { accessToken, expires, refreshToken, tokenType } = tokenInfo;

        const Authorization = `${tokenType}  ${accessToken}`;
        // 将获取到的数据存储到Storage
        wx.setStorageSync('Authorization', Authorization);
        wx.setStorageSync('expires', `${new Date().getTime() + expires * 1000}`);
        wx.setStorageSync('refreshToken', refreshToken);


        app.globalData.currentCompany = res.currentCompany;
        app.globalData.companyInfo = res.companyInfo;
        // 跳转到首页    
        wx.redirectTo({
          url: '/pages/index/index',
        })
      } 
    } catch (e) {
    }
  },

参考文档

  1. 《看完就懂系列》项目中的权限管理复杂吗?-掘金