background
Our products need to have a lot of rights management. There is a product, before the need to deploy multiple clients in the background. In the development of the first version, code-all. Three front-end, back-end three sets. Plus kafka, redis, algorithms, database server, every new customer would have a need to deploy once, it takes a long time and the code difficult to maintain.
After deciding to refactor the code, before the products are divided into, during and after the three platforms. Front and rear ends, respectively, of a code, support for rights management, can be expanded. Front-end platform using the route prefix judge, will return different token and user login information. Different token can only access the interface corresponds to the platform, the Build menu accessible based on user roles, go to a different system
Foreword
Permissions module is more troublesome part for a project, usually a project of rights management, need to do is the following three levels of authentication.
- Platform level
- Page level (menu)
- Level controls (such as buttons, tables showing fields, etc.)
This article standing in front of the point of view, to achieve the first two levels of rights management (control levels can be achieved by rendering conditions). Vue in the background with the right management templates from the front to build a zero. For reference.
Demo Address: http://auth.percywang.top
Project Address: https://github.com/pppercyWang/vue-authentication
In fact, most of the projects will separate front and back, because integrated set of code, indeed package optimization, code division needs to do more. And infrastructure projects will be some complex, more comprehensive safety considerations. There is also provided a background of pure rights management templates.
Project Address: https://github.com/pppercyWang/vue-authentication2
Project structure
Technology stack: vue vue-router vuex element
assets 静态资源
plugins
element-style.scss element样式
element.js 按需引入
router
index.js 静态路由及createRouter方法
service
api.js 前中后台接口管理
store vuex
utils
http.js axios封装
views
foreground 前台页面
midground 中台页面
background 后台页面
layout 前中后台布局文件
404.vue 404页面
Login.vue 前台登录
AgentLogin.vue 中台登录
AdminLogin.vue 后台登录
permission.js 动态路由 前中后台鉴权 菜单数据生成
main.js 应用入口
A routing initialization --staticRoutes
Three platforms to log three different pages. / The beginning of the route the front desk, / agent is in Taiwan, / admin is the background. Redirects here can also jump to a specific page, but here because of the different roles of authority reasons, can not be written to death, redirected directly to the login page.
Note: 404 final surface needs to be placed routing, dynamic routing so on the part
router/index.js
const staticRoutes = [{
path: '/login',
name: '用户登录',
component: () => import('@/views/Login.vue'),
},
{
path: '/agent/login',
name: '中台登录',
component: () => import('@/views/AgentLogin.vue'),
},
{
path: '/admin/login',
name: '后台登录',
component: () => import('@/views/AdminLogin.vue'),
},
{
path: '/',
redirect: '/login',
},
{
path: '/agent',
redirect: '/agent/login',
},
{
path: '/admin',
redirect: '/admin/login',
},
]
II. Dynamic routing --dynamicRoutes
Only in this case middle and back office for authentication, a column required field icon, the icon for the menu item. children as a sub-section columns, roles array represents the meta accessible role of the route.
permission.js
const dynamicRoutes = {
// 前台路由
'user': [{
path: '/',
component: () => import('@/views/layout/Layout.vue'),
name: '首页',
redirect: '/home',
children: [{
path: 'home',
component: () => import('@/views/foreground/Home.vue'),
}]
}, ],
// 中台路由
'agent': [{
path: '/agent/member',
component: () => import('@/views/layout/AgentLayout.vue'),
name: '会员管理',
redirect: '/agent/member/index',
icon: 'el-icon-star-on',
children: [{
path: 'index',
component: () => import('@/views/midground/member/Index.vue'),
name: '会员列表',
meta: {
roles: ['super_agent', 'second_agent'] // 超级代理和二级都可访问
},
},
{
path: 'scheme',
component: () => import('@/views/midground/member/Scheme.vue'),
name: '优惠方案',
meta: {
roles: ['super_agent'] // 只有超级代理可访问
},
},
]
},
],
// 后台路由
'admin': [{
path: '/admin/user',
component: () => import('@/views/layout/AdminLayout.vue'),
name: '用户管理',
redirect: '/admin/user/index',
icon: 'el-icon-user-solid',
children: [{
path: 'index',
component: () => import('@/views/background/user/Index.vue'),
name: '用户列表',
meta: {
roles: ['super_admin', 'admin']
},
},
{
path: 'detail',
component: () => import('@/views/background/user/UserDetail.vue'),
name: '用户详情',
meta: {
roles: ['super_admin']
},
},
]
},
],
'404': {
path: "*",
component: () => import('@/views/404.vue'),
}
}
III. Login page
Usually after a successful login, the backend will return token with user information, we need to token user information with persistence, easy to use, and here I direct presence of sessionStorage. Re-entering the different routes according to different user roles
views/adminLogin.vue
try {
const res = await this.$http.post(`${this.$api.ADMIN.login}`, this.form.loginModel)
sessionStorage.setItem("adminToken", res.Data.Token);
const user = res.Data.User
sessionStorage.setItem(
"user",
JSON.stringify({
username: user.username,
role: user.role,
ground: user.ground // 前中后台的标识 如 fore mid back
})
);
switch (user.role) {
case "ip_admin": // ip管理员
this.$router.push("/admin/ip/index");
break;
case "admin": // 普通管理员
this.$router.push("/admin/user/index");
break;
case "super_admin": // 超级管理员
this.$router.push("/admin/user/index");
break;
}
} catch (e) {
this.$message.error(e.Message)
}
IV. Routing guard --router.beforeEach ()
As long as enter the login page, we need to do two things.
- token information and user information stored in the clear in the sessionStorage
- Use createRouter permission.js provided () creates a new instance of the router, replace matcher.
We are here to use addRoutes Add a new route on the basis of static routes, but the document does not provide delete routes api. Can imagine, if the login and then log back in Taiwan, the situation in Taiwan can access the back-end routing occurs. Why replace matcher can delete a route addRoutes added?
Note: Before router.beforeEach must be placed vue instance is created, or when a page refreshes when the route will not hook into beforeEach
main.js
router.beforeEach((to, from, next) => {
if (to.path === '/login' || to.path === '/agent/login' || to.path === '/admin/login') {
sessionStorage.clear();
router.matcher = createRouter().matcher // 初始化routes,移除所有dynamicRoutes
next()
return
}
authentication(to, from, next, store, router); //路由鉴权
})
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
V. backstage before the authentication --authentication ()
Here switch function based to.path.split ( "/") [1 ] determined internet. After successful login we sessionStorage.setItem () to save token.
Why use token agentToken adminToken three different key to store it? Rather than just the token as a key yet. Such set axios.interceptors.request.use interceptor token head does not need to get a different token through the switch.
Because we assume that the current page is routing agent / member / index, we manually modify the admin / xxx / xxx. We want it to jump to the admin login page, rather than 404 pages.
isAuthentication identity authentication is completed, there is no authentication is called generateRoutes obtain a valid route, add a new route through addRoutes
permission.js
export function authentication(to, from, next, store, router) {
let token;
switch (to.path.split("/")[1]) {
case 'agent':
token = sessionStorage.getItem('agentToken');
if (!token && to.path !== '/agent/login') {
next({
path: '/agent/login'
})
return
}
break;
case 'admin':
token = sessionStorage.getItem('adminToken');
if (!token && to.path !== '/admin/login') {
next({
path: '/admin/login'
})
return
}
break;
default:
token = sessionStorage.getItem('token');
if (!token && to.path !== '/login') {
next({
path: '/login'
})
return
}
break;
}
const isAuth = sessionStorage.getItem('isAuthentication')
if (!isAuth || isAuth === '0') {
store.dispatch('getValidRoutes', JSON.parse(sessionStorage.getItem('user')).role).then(validRoutes => {
router.addRoutes(validRoutes)
sessionStorage.setItem('isAuthentication', '1')
})
}
next();
}
Judging by user.ground platform
store/index.js
getValidRoutes({commit}, role) {
return new Promise(resolve => {
let validRoutes
switch (JSON.parse(sessionStorage.getItem('user')).ground) {
case 'fore':
validRoutes = generateRoutes('user', role, commit)
resolve(validRoutes);
break
case 'mid':
validRoutes = generateRoutes('agent', role, commit)
resolve(validRoutes);
break
case 'back':
validRoutes = generateRoutes('admin', role, commit)
resolve(validRoutes);
break
}
})
},
VI. Role screening --generateRoutes ()
Here did two of the most important things
- Generating menu data in the el-menu
- Generate effective role of the current route
permission.js
export function generateRoutes(target, role, commit) {
let targetRoutes = _.cloneDeep(dynamicRoutes[target]);
targetRoutes.forEach(route => {
if (route.children && route.children.length !== 0) {
route.children = route.children.filter(each => {
if (!each.meta || !each.meta.roles) {
return true
}
return each.meta.roles.includes(role) === true
})
}
});
switch (target) {
case 'admin':
commit('SET_BACKGROUD_MENU_DATA', targetRoutes.filter(route => route.children && route.children.length !== 0)) // 菜单数据是不需要404的
break
case 'agent':
commit('SET_MIDGROUD_MENU_DATA', targetRoutes.filter(route => route.children && route.children.length !== 0))
break
}
return new Array(...targetRoutes, dynamicRoutes['404'])
}
VII. After the page refreshes data loss
After logging isAuthentication 1, will not regenerate the routing is refreshed, resulting in data loss, in main.js listening to window.onbeforeunload
main.js
window.onbeforeunload = function () {
if (sessionStorage.getItem('user')) {
sessionStorage.setItem('isAuthentication', '0') // 在某个系统登录后,页面刷新,需重新生成路由
}
}
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
expand
It is almost time you're done, simply render data to el-menu can be.
1. Background control authority
Current routing authentication control substantially by the front end, a rear end platform identification just return and role. However, the actual development, through the background are sure to storage control, menus and other information needed to build character table. To modify the column name, a column icon, menu and other rights
we can get a table at getValidRoutes permission, it will insert the data into dynamicRoutes in. The rear end of the returned data as follows:
[{
id: 1,
name: '用户管理',
icon: 'el-icon-user-solid',
children: [{
id: 3,
name: '用户列表',
meta: {
roles: [1, 2]
},
},
{
id: 4,
path: 'detail',
name: '用户详情',
meta: {
roles: [1]
},
},
]
},
{
id: 2,
name: 'IP管理',
icon: 'el-icon-s-promotion',
children: [{
id: 5,
name: 'IP列表',
meta: {
roles: [1, 2, 3]
},
}, ]
},
]
2. safety
front end:
- Cross-platform into the routing platform to jump directly to the login page.
- The current platform does not have permission to access a page report 404 errors.
rear end:
- It must ensure that the appropriate platform token can only transfer the corresponding interface, otherwise an error.
- If we could do the role of the interface authentication better, rejected a request from the interface level
3.axios package
Request interceptor take different token user information, the header information provided
in the response interceptor, if the token expires, then jump to the login page and different information
4.api management
If the backend is also a code. Api that can be managed this way, but if you do not have a unified prefix. Can for example proxy, this would resolve the problem of cross-domain in axios set up a unified prefix.
const USER = 'api'
const AGENT = 'agent'
const ADMIN = 'admin'
export default {
USER: {
login: `${USER}/User/login`,
},
AGENT: {
login: `${AGENT}/User/login`,
uploadFile: `${AGENT}/Utils/uploadFile`,
},
ADMIN: {
login: `${ADMIN}/User/login`,
},
}
devServer: {
proxy: {
'/proxy': {
target: 'http://localhost:8848',
changeOrigin: true,
pathRewrite: {
'^proxy': '' //将url中的proxy子串去掉
}
}
}
},