登录鉴权
基本登录处理
登录流程
-
校验并提交登录表单
{ username: "xxx", password: "xxx" } -
登录请求(request)成功,返回auth_token
-
存储autn_token串(localStorage),跳转至首页
-
所有请求的header头的PcAuthToken字段值为auth_token值
-
检测登录状态
auth.js:Vue Router添加前置路由(beforeEach)处理,对白名单(whiteList)之外路径跳转时判断登录状态
request.js:响应错误判断,返回http状态码403或其它约定登录失效码,检测为’登录过期’
注: auth_token也可以由服务端直接存取至cookie中,前端只需检测响应错误判断;
独立登录处理
将登录/退出登录处理逻辑独立到Vuex中供全局调用
// ./store/user.js
// getters
userInfo: state => state.userInfo,
roleInfo: state => state.roleInfo,
loginState: () => !!window.localStorage.getItem("auth_token") ? "online" : "offline",
// mutations
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo;
},
SET_ROLE_INFO(state, { roleNames, roleList }) {
// ...other roleInfo handle
state.roleInfo = { roleNames, roleList: list };
setAdminRoutes($router);
},
CLAER_ALL_INFO() {
window.localStorage.removeItem('auth_token');
clearAdminRoutes($router);
state.userInfo = null;
state.roleInfo = null;
}
// actions
login(state,token){
return api.login(form).then((res) => {
// ...other userInfo handle
window.localStorage.setItem('auth_token', jwtToken);
context.commit('SET_USER_INFO', {...});
context.commit('SET_ROLE_INFO', {...});
});
},
logout(state, token){
// ...other userInfo handle
api.logout().finally(() => context.commit('CLAER_ALL_INFO'));
}
相关概念
session与token区别
session:为了维护用户登录状态,服务端在每个用户登录后存一个唯一sessionId至服务器,每次业务请求时查询sessionId是否存在,存在即用户已登录,可进行下一步处理;
token:服务端不再存储用户登录标识,用户登录成功后生成一个包含用户id、有效期、签名的加密串,每次业务请求时解密token,校验token,校验通过则进行下一步处理;
JWT(Json Web Token):以json格式传递token的规范,标准token由3部分拼接而成:header(base64转换的签名声明),payload(base64转换的自定义数据),signature(对前面2个内容生成加密串的base64编码)
记住密码
// 2. 进入登录,查询cookies
created(){
if(this.$cookies.isKey("userName") && this.$cookies.isKey("password")){
this.username = this.$cookies.get('username');
this.password = Base64.decode(this.$cookies.get('password'));
}
}
// 1. 登录成功,判断勾选
login(){
...
if(this.isRemberPassword){
// VueCookies 与 js-base64
this.$cookies.set("username", this.loginForm.username, "3d");
this.$cookies.set("password", Base64.encode(this.$cookies.get('password')), "3d");
}else{
this.$cookies.remove("username");
this.$cookies.remove("password");
}
}
注: 纯前端方式记住密码有风险,谨慎使用;
权限控制
路由权限
以通过后端单独接口返回用户权限信息情况下为例
- router.config.js 声明adminRoutes(所有权限路由)
- 获取用户role标识,调用addAdminRoutes生成最终权限路由
// ./router/auth.js
const setAdminRoutes = function (router) {
const list = $store.getters['user/roleList'];
const adminRouteTree = deepClone(adminRoutes);
// 1. 根据权限id列表 生成 路由列表
const rootRoutes = [];
adminRouteTree.forEach((route) => {
// 检测一级路由权限
const rootId = route.meta.rootId;
if (rootId && !list.find((item) => item.id === rootId)) return;
// 检测二级路由权限
const childRouteArr = route.children.filter((secondRoute) => {
// 比较角色权限与路由rootId
const rootId = secondRoute.meta.rootId;
if (rootId && !list.find((item) => item.id === rootId)) return false;
return true;
});
route.children = childRouteArr;
router.addRoute(route);
rootRoutes.push(route);
});
router.addRoute(errorRoute);
$store.commit('SET_ADMIN_ROUTES', rootRoutes);
};
// ./router/index.js beforeEach拦截
if (!$store.getters["user/userInfo"]) {
$store
.dispatch('user/getRoleInfo')
.then(() => next(to.fullPath))
.catch((e) => {
next('/login');
});
}
```js
const setAdminRoutes = function (router) {
let list = $store.getters['user/roleList'];
let adminRouteTree = deepClone(adminRoutes);
// 递归遍历所有层级
let rootRoutes = (function filterRoutesByRoleList(routeTree) {
let temp = [];
routeTree.forEach((route) => {
// 此处对比权限根据实际需求确定
let rootId = route.meta.rootId;
if (rootId && !list.find((item) => item.id === rootId)) return;
if (route.children) route.children = filterRoutesByRoleList(route.children);
temp.push(route);
});
return temp;
})(adminRouteTree);
// VueRouter的addRoutes旧方法,一次添加多个路由
router.addRoutes([...rootRoutes, errorRoute]);
$store.commit('SET_ADMIN_ROUTES', rootRoutes);
};
控件权限
权限控制(rootId)表
// constants/rootId.js
export const ROOT_MAP = new Map([
['权限1名称', 100],
['权限2名称', 100],
['权限3名称', 100],
...
]);
// 多模块嵌套
export const MODULE_ROOT_MAP = new Map([
[1, new Map([
['模块1权限1名称', 100],
['模块1权限2名称', 100],
['模块1权限3名称', 100],
...
])],
...
]);
自定义指令(directive)快速控制
import $store from '@/store';
const detectPermission = {};
detectPermission.install = (Vue) => {
Vue.directive('detect-permission', (el, binding, vnode) => {
// 匹配权限
if ($store.getters['user/roleList'].find((item) => item.id === binding.value)) return;
if (el.parentNode) el.parentNode.removeChild(el);
});
};
export default detectPermission;
// Vue.use(detectPermission)
权限判断(hasRoot、hasSomeRoot)
// store/modules/user.js
hasRoot: (state) => (rootId) => !!getters.roleList(state).find((item) => item.id === rootId),
hasSomeRoots: (state) => (rootMap, keys) => keys.some((key) => getters.hasRoot(state)(rootMap.get(key)))
指令/hasRoot使用
<img v-detect-permission="ROOT_MAP.get('封面图')"/>
<div v-detect-permission="MODULE_ROOT_MAP.get(moduleIndex).get('编辑')" >编辑</div>
<div v-if="hasSomeRoot(MODULE_ROOT_MAP.get(moduleIndex), ['添加','删除','新增'])">
<div v-detect-permission="MODULE_ROOT_MAP.get(moduleIndex).get('添加')" >添加</div>
<div v-detect-permission="MODULE_ROOT_MAP.get(moduleIndex).get('删除')" >删除</div>
<div v-detect-permission="MODULE_ROOT_MAP.get(moduleIndex).get('新增')" >新增</div>
</div>
if(this.hasRoot(ROOT_MAP.get('查询列表'))) this.$req.getList();
思考:
- 可以将权限控制独逻辑立为一个组件容器,在需要控制权限内容上包一层;
- 请求的权限控制不应该通过单独判断,可以请求上注明权限信息,全局请求拦截器统一判断拦截;