前端权限处理,说难不难,说易不易。但是理清思路的话就豁然开朗--就是不同的用户具有不同的权限,然后根据权限限制用户访问页面和点击按钮
前置知识
用户并不直接对应权限,用户对应的其实是角色 。
也就是说:用户对应角色,角色对应权限。如果我们有一百个用户,但是他们等级不同,老板有全部的权限,经理有80%权限,组长有50%权限,职员有20%权限。
那么其实我们只需要4个角色:
- 老板角色 有全部的权限,
- 经理角色 有80%权限,
- 组长角色 有50%权限,
- 职员角色 20%权限。
用户只要对应这4个角色即可,不同级别的用户就能有不同的权限了。
接口权限
调用权限验证接口没有通过时,后端一般返回401 Authentication Required
, 前端则返回登录页重新登录
登录完拿到Token,将Token存起来(cookie/localStorage
),每次调用业务接口时,请求头需要携带Token
401
用户没有权限(令牌、用户名、密码错误),主要情景包括但不限于以下
- 登录失败-用户名、密码错误
- Token过期
403
用户得到授权,但是访问是被禁止的
- 登录成功,并且Token有效, 但是此条接口没有授予访问权限
登录权限逻辑
所谓登录权限,就是用户输入账密码后,点击登录按钮(调用登录API)能否正常登录系统。
1. 调用登录接口
登录成功后,接口可能返回的数据类型主要有以下情景
- 情景二: 返回用户信息(包括 Token, 菜单-可以访问的页面 姓名,电话.....)
- 情景二: 实际开发中一般是这个场景
- 2.1. 返回用户信息(包括 Token, 姓名,电话.....)
- 2.2. 调用获取用户信息的接口
- 2.3. 调用获取菜单的接口,返回当前用户可以访问的菜单列表
如果调用登录接口成功,则将Token存储到 cookie/localStorage
中;
如果调用登录接口失败,则只在登录界面出现错误提示(不做其他多余动作)
2. 调用获取用户信息的接口
返回用户信息( 等更详细的用户信息.....),将数据存储到全局(如Redux
)中,供其他业务界面使用
3. 调用获取菜单的接口
现代前端基本都是单页面应用开发,如Vue,React,都是通过一份路由文件来对系统菜单及页面进行配置。
注:菜单-路由-页面 虽然界面展现形式不同,但都可以理解为同一个意思
- 前端项目一般都会有一个菜单配置文件(包含了此系统所有的页面菜单) 菜单A ;
- 调用菜单接口成功后,返回了菜单(包含了当前用户可以访问的页面菜单),**菜单B **;
- 2.1. 如果 菜单B 返回数据为空,则跳转到
403
界面 - 2.2. 如果 菜单B 有数据,则执行下面的步骤
- 2.1. 如果 菜单B 返回数据为空,则跳转到
- 将菜单A数据与 菜单B 数据对比(主要逻辑是进行双重循环), 如果存在当前用户无权访问的菜单数据,则新加一个 类似
authority=false
(无权限)的字段,表示该页面没有权限; - 处理后得到最新的一份菜单数据-菜单C,取出此数据中 第一个
authority=true
(有权限) 的数据,并跳转到这个界面
以上,整个登录逻辑大致如此
页面访问权限
菜单可以动态。路由一定是静态的,一定是提前写死的,不管是路由配置还是约定式。他都是要穷尽你项目中的可访问路由的。然后菜单才是可选的,比如你只给A用户显示三个路由菜单。然后在菜单权限这里判断,用户当前访问路由,不在这个权限列表中时,跳转到403页面。
逻辑流程图
在路由拦截中判断当前用户是否具有对应的页面路由权限
页面鉴权逻辑处理中
页面鉴权逻辑处理中,应该出现 loading
页面
403页面
一般情况, 用户登录成功后界面返回了指定的菜单, 某些菜单/页面没有权限,则应该跳转到 403
页面
404页面
当用户在浏览器中输入 系统中不存在路由地址,则应该跳转到 404页面
按钮权限
举个栗子:
-
用户A进入页面,用户A对应的角色是
职员
,所有职员
角色进入页面,页面内都会有增加
,查找
两个选项; -
用户B进入页面,用户B对应的角色是
组长
,所有组长
角色进入页面,页面内都会页面内有增加
,查找
,修改
,删除
四个选项; -
用户C进入页面,用户C没有对应的角色,没有对应角色的用户进入页面,页面都会提示
权限不足,3秒后将跳转首页;
思路
一个页面会有新增,删除,编辑等等按钮。不同用户应该是有不同操作权限的。我们不妨定义权限码
- 0:不可见
- 1:不可编辑
- 2:可编辑
我们提前和后端约定好按钮的名字,后端会返回一个按钮权限列表。获取此列表的时机可以分为以下几种
- 在获取用户信息的接口中,返回所有页面及其按钮权限的映射关系,存储到 全局(如Redux)中
- 路由拦截: 用户访问某一个具体页面的时候,再重新获取 当前页面的按钮权限
然后我们根据权限列表给按钮绑定disabled
属性达到相应权限效果。实质就是根据相应权限操作DOM
二 .移动端权限验证
与web端逻辑类似
react-native 权限验证
进入app首先会进入一个权限验证页面,此页面可以是一个欢迎页,在此欢迎页中从Storage中取Token
- 如果没有Token则到登录界面;
- 如果有Token,则根据Token获取当前用户信息;
- 如果接口返回登录失效,则到登录界面
- 如果接口返回成功则跳转到业务界面首页
小程序登陆鉴权流程
上面是小程序官方文档的登陆鉴权流程图
登陆方式一(需要手机号,用户名称等信息...)
- 在登录页面的
onLoad
函数中- 1.1 调取
wx.login
获取code
字段 - 1.2 调用后端登陆接口(需要传递的参数为
{code:string}
),即可返回openid
,sessionid
等
- 1.1 调取
/**
* 生命周期函数--监听页面加载
*/
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 })
}
}
})
},
- 点击登录按钮
- 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) {
}
},