Use Vue/React two-way binding framework to do the background system is more suitable, however, compared with common front-end projects in the background system in addition to more frequent data interaction, there is a special requirement is to control the user’s permission, so how to achieve permission control in a Vue application? Here’s a little bit of my experience.
What is access control
In the world of permissions, everything provided by the server is the resource, which can be described by the request method + request address. Permissions are the permission to access specific resources. The so-called permissions control is to ensure that users can only access allocated resources. Specifically, front-end access to resources is usually initiated by buttons on the interface, such as deleting a certain piece of data; Or initiated by a user going to a page, such as fetching list data. These two forms cover most scenarios of resource requests, so permission control can also be broadly divided into menu permission control and button permission control.
Vue menu permission control
Menu is a direct representation of routing. Menu control is actually routing control. A simple way to implement route control is to check whether the current route address to be jumped is accessible in the before hook of the route, and determine whether to permit the route according to the check result. Pseudo-code:
router.beforeEach((to, from, next) => {
// Check permissions
let pass = valid(to);
if(! pass){
Return console.log(' no access ');
}
next();
});
Copy the code
This approach is simple and intuitive, and is ideal for simple systems, but it essentially registers all routes, which has two immediate disadvantages: if the routing component is not loaded on demand, the application will load a lot of redundant code; Second, it is a waste of computing power to traverse the complete route for each jump, and it is not desirable for applications with a large number of routes.
The ideal implementation method is to save the complete route locally, but do not initialize the Vue application immediately. After the user logs in and gets the permission, use the menu permission to screen out the available route, and then initialize the Vue application with the available route. In other words, the login page should be made into a separate page. After login, the user data should be saved locally, and then the PAGE where the Vue application is located should be jumped to through THE URL. Before starting the Vue application, the route screening should be completed through the local user data, and then the Vue application should be initialized.
//main.js
let user = sessionStorage.getItem('user');
if (user) {
user = JSON.parse(user);
// Filter to get the actual route
let fullPath = require('fullPath.js');
let routes = filter(fullPath, user.menus);
// Create a routing object
let router = new Router({routes});
// Generate a Vue instance
new Vue({
el: '#app',
router,
render: h => h(App)
});
}else{
location.href = '/login/';
}
Copy the code
In this way, we generate a set of “customized” routes according to user permissions. At this time, we also hope to generate navigation menus directly with routes. Conventional routing data may not meet the requirements of menu components, so we can maintain the menu data in the meta data of routes in advance, such as menu names, menu ICONS, etc. The current routing data can be accessed by simply using $router.options in the template. If implemented using the element-UI menu component, the code would look something like this:
<el-menu router>
<el-menu-item v-for="(route, index) in $router.options.routes[2].children"
:route="route"
:index="route.name">
<i class="ion" v-html="route.icon"></i>{{route.name}}
</el-menu-item>
</el-menu>
Copy the code
Of course, this can only loop out the first level menu, if there is a second level route corresponding to the second level menu, we need to judge and loop the children node, relatively simple without putting more code, menu permission control is completed here.
Vue button permission control
The implementation idea of button permission control is similar to that of menu permission control. It also determines whether the display of each button is displayed or not according to the user permission. The method is no more than V-IF or custom instruction. However, the characteristic of V-IF is that it will respond to data changes, so the permission verification will be triggered frequently with the running of the application, and the permission only needs to be verified once in the whole application life cycle. In order to avoid unnecessary program execution, a custom instruction can be used to achieve this, pseudo-code:
Vue.directive('has', {
bind: function (el, binding) {
if(! has(binding.value)){
el.parentNode.removeChild(el);
}
}
});
/ / usage:
< BTN v-has='get,/sources'> button </ BTN >
Copy the code
Note that there is a has() method in the bind callback, which is the permission check method. We also globally mix this method into the Vue object, making it accessible to every component in the application, to support v-if on the interface, for example:
<div v-if="has('get,/sources') && something">
A div that requires both the 'get,/sources' permission and somthing to be true
</div>
Copy the code
In this way, any need to achieve according to the authority of the button explicit control and interface changes can be very convenient to achieve.
But the real problem with button permissions is not how to implement them, but the high maintenance costs. We assume that the button Btn is bound to click callback Fn, and the callback Fn sends a request for Req, which requires access to a resource. Ultimately, you have to decide whether to display Btn or not according to whether the user has the permission to Req. Req is not directly related to Btn, so we need to maintain their relationship with human beings. It is normal to have dozens or hundreds of buttons in a complex project. It is a headache to maintain the permissions of so many buttons as the business changes.
One way to get around this mess is for the front end to give up control of the visual layer, back to the request layer, and focus on intercepting the request before it is initiated. In this case, permissions can be verified directly based on the request method and the request address. No additional code is required except to implement an interceptor. In the case of Axios, the interceptor looks something like this:
axios.interceptors.request.use(function (config) {
if(! has(config)){
// Verification failed
return Promise.reject({
message: `no permission`
});
}
return config;
});
Copy the code
However, if permission control is only done in this way, all buttons will be displayed on the interface, but users may not be able to click the buttons they see. I think this kind of experience can only stay at the theoretical level and cannot be applied to actual products at all. Request control can be used as the second line of defense of the entire control system, or as an auxiliary means in some special cases, and ultimately return to the idea of button control.
So how to collect the required permissions for each button as easily as possible? There are two layers between buttons and permissions. The first layer is the Click callback, and the second layer is the AJAX request in the callback. If you don’t want human maintenance, you need to find a way to break the two layers and make the button and permissions related. Let things happen naturally, like this,
< BTN v-do="Fn"> button </ BTN >
Copy the code
If Fn can in some form collected inside the AJAX request parameters, and translating permission information transfer out is perfect, but I didn’t find a feasible method, and this also exist defects in the application form, because not every action buttons initiate an AJAX request, such as the edit button itself will not trigger a request, What really triggers the request is another save button, so the idea is just nice to look at.
So the next best thing to do is to associate the button with the request, so let’s say the button involves A request called A, so I want the permission directive to say,
< BTN v-has="A" @click="Fn">
Copy the code
It’s far from perfect, but it doesn’t require manual maintenance to the ‘get,/resources’ level, where implementations of A can take many forms. For example, A can be an object with two attributes:
const A = {
p: ['put,/menu/**'],
r: params => {
return axios.put(`/menu/${params.id}`, params)
}
};
// Use as permission:
< BTN v-has="[A]" @click="Fn"> </ BTN >
// as a request:
function Fn(){
A.r().then((res) => {})
}
Copy the code
Usually, we put all the APIS in the project into one API module for centralized management. When we write the API, we give the permission to the maintenance incidentally. In exchange, we can describe the permission directly with the request name in the component interface, and there is no need to go back and forth between the interface and the API module, which realizes the separation of concerns to some extent. Moreover, the has instruction can be further optimized. For example, the parameter only needs to receive A, and the instruction automatically accesses A.p to obtain permissions according to the convention. It can also receive arrays, allowing multiple permissions to be checked jointly.
Afterword.
Ok, this is my front-end authority control some practice and thinking, if there is improper welcome to correct.
One final tease about Element-UI, which is ugly.
http://refined-x.com/2017/08/29/ Based on Vue to achieve background system permission control /