初识ABP vNext(4):vue用户登录菜单权限

点击上方蓝字"小黑在哪里"关注我吧

  • 登录

  • 菜单权限

  • 运行测试

前言

上一篇已经创建好了前后端项目,本篇开始编码部分。

开始

几乎所有的系统都绕不开登录功能,那么就从登录开始,完成用户登录以及用户菜单权限控制。

登录

首先用户输入账号密码点击登录,然后组合以下参数调用identityserver的/connect/token端点获取token:

{grant_type: "password",scope: "HelloAbp",username: "",password: "",client_id: "HelloAbp_App",client_secret: "1q2w3e*"
}

这个参数来自ABP模板的种子数据:

我使用的是password flow,这个flow无需重定向。如果你的网站应用只有一个的话,可以这么做,如果有多个的话建议采用其他oidc方式,把认证界面放到identityserver程序里,客户端重定向到identityserver去认证,这样其实更安全,并且你无需在每个客户端网站都做一遍登录界面和逻辑。。。

还有一点,严格来说不应该直接访问/connect/token端点获取token。首先应该从identityserver发现文档/.well-known/openid-configuration中获取配置信息,然后从/.well-known/openid-configuration/jwks端点获取公钥等信息用于校验token合法性,最后才是获取token。ABP的Angular版本就是这么做的,不过他是使用angular-oauth2-oidc这个库完成,我暂时没有找到其他的支持password flow的开源库,参考:https://github.com/IdentityModel/oidc-client-js/issues/234

前端想正常访问接口,首先需要在HttpApi.Host,IdentityServer增加跨域配置:

前端部分需要修改的文件太多,下面只贴出部分主要代码,需要完整源码的可以去GitHub拉取。

src\store\modules\user.js:

const clientSetting = {grant_type: "password",scope: "HelloAbp",username: "",password: "",client_id: "HelloAbp_App",client_secret: "1q2w3e*"
};
const actions = {// user loginlogin({ commit }, userInfo) {const { username, password } = userInforeturn new Promise((resolve, reject) => {clientSetting.username = username.trim()clientSetting.password = passwordlogin(clientSetting).then(response => {const data = responsecommit('SET_TOKEN', data.access_token)setToken(data.access_token).then(() => {resolve()})}).catch(error => {reject(error)})})},// get user infogetInfo({ commit }) {return new Promise((resolve, reject) => {getInfo().then(response => {const data = responseif (!data) {reject('Verification failed, please Login again.')}const { name } = datacommit('SET_NAME', name)commit('SET_AVATAR', '')commit('SET_INTRODUCTION', '')resolve(data)}).catch(error => {reject(error)})})},setRoles({ commit }, roles) {commit('SET_ROLES', roles)},// user logoutlogout({ commit, dispatch }) {return new Promise((resolve, reject) => {logout().then(() => {commit('SET_TOKEN', '')commit('SET_NAME', '')commit('SET_AVATAR', '')commit('SET_INTRODUCTION', '')commit('SET_ROLES', [])removeToken().then(() => {resetRouter()// reset visited views and cached views// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485dispatch('tagsView/delAllViews', null, { root: true })resolve()})}).catch(error => {reject(error)})})},// remove tokenresetToken({ commit }) {return new Promise(resolve => {commit('SET_TOKEN', '')commit('SET_NAME', '')commit('SET_AVATAR', '')commit('SET_INTRODUCTION', '')commit('SET_ROLES', [])removeToken().then(() => {resolve()})})}
}

src\utils\auth.js:

export async function setToken(token) {const result = Cookies.set(TokenKey, token);await store.dispatch("app/applicationConfiguration");return result;
}export async function removeToken() {const result = Cookies.remove(TokenKey);await store.dispatch("app/applicationConfiguration");return result;
}

src\api\user.js:

export function login(data) {return request({baseURL: "https://localhost:44364",url: "/connect/token",method: "post",headers: { "content-type": "application/x-www-form-urlencoded" },data: qs.stringify(data),});
}export function getInfo() {return request({url: "/api/identity/my-profile",method: "get",});
}export function logout() {return request({baseURL: "https://localhost:44364",url: "/api/account/logout",method: "get",});
}

src\utils\request.js:

service.interceptors.request.use((config) => {// do something before request is sentif (store.getters.token) {config.headers["authorization"] = "Bearer " + getToken();}return config;},(error) => {// do something with request errorconsole.log(error); // for debugreturn Promise.reject(error);}
);// response interceptor
service.interceptors.response.use((response) => {const res = response.data;return res;},(error) => {console.log("err" + error); // for debugMessage({message: error.message,type: "error",duration: 5 * 1000,});if (error.status === 401) {// to re-loginMessageBox.confirm("You have been logged out, you can cancel to stay on this page, or log in again","Confirm logout",{confirmButtonText: "Re-Login",cancelButtonText: "Cancel",type: "warning",}).then(() => {store.dispatch("user/resetToken").then(() => {location.reload();});});}return Promise.reject(error);}
);

菜单权限

vue-element-admin的菜单权限是使用用户角色来控制的,我们不需要role。前面分析过,通过/api/abp/application-configuration接口的auth.grantedPolicies字段,与对应的菜单路由绑定,就可以实现权限控制了。

src\permission.js:

router.beforeEach(async (to, from, next) => {// start progress barNProgress.start();// set page titledocument.title = getPageTitle(to.meta.title);let abpConfig = store.getters.abpConfig;if (!abpConfig) {abpConfig = await store.dispatch("app/applicationConfiguration");}if (abpConfig.currentUser.isAuthenticated) {if (to.path === "/login") {// if is logged in, redirect to the home pagenext({ path: "/" });NProgress.done(); // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939} else {//user nameconst name = store.getters.name;if (name) {next();} else {try {// get user infoawait store.dispatch("user/getInfo");store.dispatch("user/setRoles", abpConfig.currentUser.roles);const grantedPolicies = abpConfig.auth.grantedPolicies;// generate accessible routes map based on grantedPoliciesconst accessRoutes = await store.dispatch("permission/generateRoutes",grantedPolicies);// dynamically add accessible routesrouter.addRoutes(accessRoutes);// hack method to ensure that addRoutes is complete// set the replace: true, so the navigation will not leave a history recordnext({ ...to, replace: true });} catch (error) {// remove token and go to login page to re-loginawait store.dispatch("user/resetToken");Message.error(error || "Has Error");next(`/login?redirect=${to.path}`);NProgress.done();}}}} else {if (whiteList.indexOf(to.path) !== -1) {// in the free login whitelist, go directlynext();} else {// other pages that do not have permission to access are redirected to the login page.next(`/login?redirect=${to.path}`);NProgress.done();}}
});

src\store\modules\permission.js:

function hasPermission(grantedPolicies, route) {if (route.meta && route.meta.policy) {const policy = route.meta.policy;return grantedPolicies[policy];} else {return true;}
}export function filterAsyncRoutes(routes, grantedPolicies) {const res = [];routes.forEach((route) => {const tmp = { ...route };if (hasPermission(grantedPolicies, tmp)) {if (tmp.children) {tmp.children = filterAsyncRoutes(tmp.children, grantedPolicies);}res.push(tmp);}});return res;
}const state = {routes: [],addRoutes: [],
};const mutations = {SET_ROUTES: (state, routes) => {state.addRoutes = routes;state.routes = constantRoutes.concat(routes);},
};const actions = {generateRoutes({ commit }, grantedPolicies) {return new Promise((resolve) => {let accessedRoutes = filterAsyncRoutes(asyncRoutes, grantedPolicies);commit("SET_ROUTES", accessedRoutes);resolve(accessedRoutes);});},
};

src\router\index.js:

export const asyncRoutes = [{path: '/permission',component: Layout,redirect: '/permission/page',alwaysShow: true, // will always show the root menuname: 'Permission',meta: {title: 'permission',icon: 'lock',policy: 'AbpIdentity.Roles'},children: [{path: 'page',component: () => import('@/views/permission/page'),name: 'PagePermission',meta: {title: 'pagePermission',policy: 'AbpIdentity.Roles'}},{path: 'directive',component: () => import('@/views/permission/directive'),name: 'DirectivePermission',meta: {title: 'directivePermission',policy: 'AbpIdentity.Roles'}},{path: 'role',component: () => import('@/views/permission/role'),name: 'RolePermission',meta: {title: 'rolePermission',policy: 'AbpIdentity.Roles'}}]},。。。。。。// 404 page must be placed at the end !!!{ path: '*', redirect: '/404', hidden: true }
]

因为菜单太多了,就拿其中的一个“权限测试页”菜单举例,将它与AbpIdentity.Roles绑定测试。

运行测试

运行前后端项目,使用默认账号admin/1q2w3E*登录系统:

正常的话就可以进入这个界面了:

目前可以看到“权限测试页”菜单,因为现在还没有设置权限的界面,所以我手动去数据库把这条权限数据删除,然后测试一下:

但是手动去数据库改这个表的话会有很长一段时间的缓存,在redis中,暂时没去研究这个缓存机制,正常通过接口修改应该不会这样。。。

我手动清理了redis,运行结果如下:

最后

本篇实现了前端部分的登录和菜单权限控制,但是还有很多细节问题需要处理。比如右上角的用户头像,ABP的默认用户表中是没有头像和用户介绍字段的,下篇将完善这些问题,还有删除掉vue-element-admin多余的菜单。

如果本文对您有用,

不妨点个“”或者转发朋友圈支持一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308204.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

满汉楼(德鲁伊连接池+DBUtils+DAO+Mysql)保姆级别分析+代码实现

一:需求 1.完成一个酒店后台管理系统,当然只是模拟,在控制台就行模拟 2.实现管理人员的登录和酒店成本账单的查看,以及正常的点餐,结账和查看账单等等功能 二:实现的功能展示 1.用户登录(这里…

[Hei-Ocelot-Gateway ].Net Core Api网关Ocelot的开箱即用版本

写在前面很多neter都有在用Ocelot做Api网关,但是Ocelot又不像kong或者其他网关一样,开箱即用。它需要你单独开一个web项目来部署,这样很多同学都在做重复的事了。这里[Hei.Ocelot.ApiGateway] 就把这件事给做了,以后有…

聊聊常见的服务(接口)认证授权

写在前面头发掉得多了,总有机会接触/调到各种各样的接口,各种面向Api编程实际上已经嵌入到我们的习惯中,没办法现在服务端通信还得是http(s),其他协议还未能成为通用的。大厂的开发平台api我先不敢说,各种…

二分查找(划分时左右元素个数不相等)解析+代码

一:问题描述 当我们在用二分法查找元素的时候,我们往往特希望遇到是奇数个元素个数的数组,因为划分完左右两边的个数相等,所以在以前刚学二分法的时候就有这个疑问,当时就是模模糊糊过去了,再遇到其实还是会有疑问。现…

网络计算机室电源线怎么布,网吧综合布线(电源和网络)经验谈

电源系统布线篇网吧目前所提供的服务,像网页浏览、网络游戏、在线电影、远程教育等最基本的服务都与网络有关,网络质量的好坏直接决定了网吧的生存能力。所以,如何规划一个优质的网络环境,是网吧经营者必须要考虑的一个要点&#…

69. Sqrt(x)010(二分法求解+详解注释)

一:题目 ‘给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0…

计算机网络 哪个教材好,学习计算机网络哪本教材最好?

benxiuxian高分答主12-29TA获得超过8057个赞地理期末复习计划临近期末,为帮助学生理顺知识,培养学生灵活运用知识分析问题,解决问题的能力,形成完整的知识体系,特作复习计划如下:一、复习目的:1…

安装VSCode作为常用的文本编辑器

作为程序员,跟文本编辑器打交道那是天天都在做的事情,一个趁手的文本编辑器能大大地提供工作效率,减少996福报。笔者使用过各种文本编辑器,或是xx版,或是免费版,多多少少都有一些不便之处,如今 …

35. 搜索插入位置011(二分查找)

一:题目: 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。 示例 1: 输入: nums [1,3,5,6], …

利用 Github Actions 自动更新 docfx 文档

利用 Github Actions 自动更新 docfx 文档Introdocfx 是微软出品一个 .NET API 文档框架,有一个理念是代码即文档,会根据项目代码自动生成 API 文档,即使没有写任何注释也会生成 API 文档,也有一些默认的主题可以配置,…

34. 在排序数组中查找元素的第一个和最后一个位置012(二分查找+思路+详解+两种方法)Come Baby!!!!!!!! !

一:题目 给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 进阶: 你可以设计并实现时间复杂度为 O(log n) 的算法解…

计算机专业可以报考放射医学吗吗,文科生可以报考医学影像技术专业吗

近期很多文科同学都在疑惑可不可以报考医学影像技术专业,这里有途网小编告诉大家,医学影像技术专业是理科专业,文科生一般来说是不可以报考医学影像技术专业的,但在每年高校招生计划发布的时候也可能会考虑招收文科生的情况&#…

基于PaaS平台的多应用自集成方案之公共数据集成

源宝导读:在明源云“天际”PaaS平台之上,可以构建、部署和运行多个业务应用,并支持多应用分离部署,以提升系统整体的性能和稳定性。本文将介绍多应用自集成解决方案以及相关的实现细节。一、背景1.1、业务场景在明源云“天际”Paa…

可能是Asp.net Core On host、 docker、kubernetes(K8s) 配置读取的最佳实践

写在前面为了不违反广告法,我竭尽全力,不过“最佳实践”确是标题党无疑,如果硬要说的话 只能是个人最佳实践。问题引出可能很多新手都会遇到同样的问题:我要我的Asp.net Core 应用传统方式直接部署(host),…

33. 搜索旋转排序数组(013)二分查找+思路详解+来干了这杯代码!!!!!!

一&#xff1a;题目 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], n…

计算机软件在矿井地质中的应用,(完整版)遥感导论知识点整理(梅安新版)

遥感导论知识点整理【题型】一、选择题二、填空题三、名词解释四、简答题五、论述题注意&#xff1a;标注页码的地方比较难理解&#xff0c;希望大家多看看书&#xff0c;看看ppt。【第一章】绪论1、【名】遥感(remote sensing)广义&#xff1a;泛指一切无接触的远距离探测&…

81. 搜索旋转排序数组 II(014)二分查找+思路+详解+二种做法

一&#xff1a;题目 已知存在一个按非降序排列的整数数组 nums &#xff0c;数组中的值不必互不相同。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转 &#xff0c;使数组变为 [nums[k], nums[k1…

学校考的计算机三级证书,全国计算机等级考试证书用途

全国计算机等级考试证书用途计算机知识浩如瀚海&#xff0c;即使任何资深人士都不可能精通或熟悉所有的计算机软件&#xff0c;但是为什么他们无论什么软件都能很快就上手会用呢&#xff0c;那是因为本质上任何计算机软件都是C语言之类的编程语言开发出来的&#xff0c;所以我们…

如何校验内存数据的一致性,DynamicExpresso 算是帮上大忙了

一&#xff1a;背景1. 讲故事记的在上一家公司做全内存项目的时候&#xff0c;因为一些关键表会在程序 startup 的时候全量灌入到内存中&#xff0c;但随着时间的推移&#xff0c;内存和数据库的同步偶尔会出现数据差异的情况&#xff0c;伴随着就是运营那边报过来的 bug&#…

临界表储存图的数据(思路+详解+图示)

一&#xff1a;前言 当我们考虑用邻接表储存数据的时候&#xff0c;一定会拿邻接矩阵和其进行比较。邻接矩阵在储存数量较小的数据是耗费的内存是要高于邻接表的。那么我们在做题的时候如果出现内存超限&#xff0c;那就要注意了&#xff0c;可以考虑换用邻接表来储存数据了 …