On the scheme of front-end role authority

FinClip How can front-end engineers control and implement different roles and permissions in the front-end, so as to control different users to access different pages?

preface

For most background management, role permissions are an important link. Through the configuration of role permissions, we can easily adjust whether users can access relevant pages.

Easy to understand, it means which pages are open to all users, which can only be accessed after logging in, which can only be accessed with xx role permissions, etc. (xx here refers to the roles of administrators).

In the background management system, the scheme design of role permission is very important. If the design is not good enough, it may cause various permission codes in the project to mix with business codes, resulting in structural confusion. Later maintenance, including adding control to new modules, will also become very troublesome.

Although the front end can do some things at the permission level, it is regrettable that the back end really checks the permission. In a software system, the interface called by the front end should not be called without permission and return data. Therefore, the interface back-end must be controlled strictly according to permissions. In short, when the front end does not write a line of permission code, when a user enters a page he does not have access to, the back end can judge his unauthorized access and refuse to return data. However, the experience of such applications is very bad, such as various error reports when accessing unauthorized pages, etc. Therefore, the more responsibility of the front end in role permissions should be to improve the user's interactive experience. Another important reason is that the authority verification done by the front end can be passed by local data fraud.

In the whole process of role permission control, the front-end process step should accept the permission data sent by the background during login or refresh, and then inject the data into the application. The whole application then starts to control the display content and navigation logic of the page, so as to achieve the purpose of permission control. Although the permission control of the front end can provide a layer of protection, the fundamental purpose is to optimize the experience.

This paper will describe the implementation of front-end role permissions from the following three aspects

  • Login permission control
  • Page permission control
  • Content permission control

Login permission control

Login permission control, in short, is to realize which pages can be accessed by unlisted users and which pages can be accessed only after users log in.

It is also very simple to realize this function. Here are two common implementation schemes.

   export const routes = [
      {
         path: '/login', //Login page
         name: 'Login',
         component: Login,
      },
      {
         path:"/register", // List page
         name:"Register",
         component: Register, 
      },
      {
         path:"/list", // List page
         name:"List",
         meta:{
            need_login:true //Login required
         }
      }
    ]

There are three pages: login page, registration page and list page. Everyone can access the login page and registration page, but the list page can only be seen after logging in. Add a meta object to the route and add need_ Set login to true.

At the code level, the above objectives can be easily achieved through router.beforeEach. Each time the page jumps, the function wrapped by router.beforeEach will be called. The code is as follows:

router.beforeEach((to, from, next) => {
  const { need_login = false } = to.meta;
  const { user_info } = store.state; //Get user login information from vuex
  if (need_login && !user_info) {
    // If the page needs to log in but the user does not log in, skip to the login page
    const next_page = to.name; // When configuring routes, name must be assigned to each route
    next({
      name: 'Login',
      params: {
        redirect_page: next_page,
        ...from.params, //If the jump needs to carry parameters, pass the parameters
      },
    });
  } else {
    //Direct release without login
    next();
  }
});

To is the routing information to be accessed, and get the need from it_ The value of login can determine whether login is required. Then get the user's login information from vuex.

If the user does not log in and the page to be accessed needs to log in again, use next to jump to the login page and route the name of the page to be accessed through redirect_page, and you can get redirect on the login page_ Page, etc. jump directly after successful login.

If you don't want to add meta, you can see another method below.

  // Introduce a page that does not require login
  import invisible from './invisible';

  let router = new Router({
    routes: [
      ...invisible,
    ],
  });

  const invisibleMap = [];
  invisible.forEach(item => {
    if (item.name) {
      invisibleMap.push(item.name);
    }
  });

  router.beforeEach(async (to, from, next) => {
    if (!invisibleMap.includes(to.name)) {
        // Business logic judgment login, etc
    }
    else {
      next();
    }
  })

Page permission control

Page permission control is mainly used to give different page access permissions to different roles. Next, let's understand the concept of roles.

In some systems with relatively simple permission settings, the first method above is sufficient. However, if the system introduces roles, it is necessary to further transform and enhance the ability of permission control on the basis of the above.

Roles appear to personalize the permission list. For example, the current system sets three roles: ordinary member, administrator and super administrator. Ordinary members can browse modules a, b and c of the software system, but they cannot view and edit modules d and E. The administrator has all the capabilities of ordinary members. In addition, it can view d and e modules. The super administrator has all the permissions of the software system. He has the ability to assign an account as an administrator or remove its identity.

Once the concept of role is introduced into the software system, each account should correspond to at least one or several roles, so as to have the corresponding permissions of one or several roles. What the front end needs to do is to give the account access and operation permission to the corresponding page according to its role identity.

The arrangement of roles such as ordinary members, administrators and super administrators is still a very simple way to divide. In actual projects, the division of roles is much more detailed. For example, some common background business systems and software systems will establish roles according to various departments of the company, such as marketing department, sales department, R & D department and so on. Each member of the company will be divided into corresponding roles to have the permissions of the role.

The concept of so many roles introduced above is mainly to understand the permission design from the dimension of the whole stack, but it is not necessary to deal with the role logic when it is really implemented in the front-end project. That part of the functions are mainly completed by the back-end.

Now let's assume that the back-end doesn't handle the problem, and the role is completely handed over to the front-end.

First, create a new configuration file in the front end. It is assumed that the current system sets three roles: ordinary member, administrator and super administrator, and the list of pages that each role can access.

 export const permission_list = {
   member:["List","Detail"], //Ordinary member
   admin:["List","Detail","Manage"],  // administrators
   super_admin:["List","Detail","Manage","Admin"]  // Super administrator
 }

Each value in the array corresponds to the name value of the front-end routing configuration. Ordinary members can access the list page and details page, administrators can access the management page, and super administrators can access the super management page.

The whole operation process is briefly described as follows. After the user logs in successfully, the known user data and role are returned through the interface. After getting the role value, take out the page list array that the role can access from the configuration file, and then load this part of permission data into the application to achieve the purpose of permission control.

From the above process, it is also possible to put roles on the front-end configuration. However, if the project has been launched, the product manager requires the project to urgently add a new role, such as X customer, and move the existing user Liu to x customer. Such changes will cause the front end to modify the code file and create a new role on the original configuration file to meet this requirement.

It can be seen that configuring the role list by the front end is very inflexible and error prone, so the best solution is to leave it to the back end for configuration. Once the user logs in, the back-end interface directly returns the permission list owned by the account. As for the role of the account and the page permissions owned by the role, the reasonable scheme should be back-end processing.

The second role permission scheme is described below.

For example, the account information structure returned by the backend is as follows

 {
  user_id:1,
  user_name:"Liu Mou",
  permission_list:["List","Detail","Manage"]
}

At this time, the front end doesn't care what roles the account has. It just needs to display the pages with permissions.

It can be seen from this that the account has the permissions of "list, details and management".

    //Static routing
    export const invisible = [
      {
         path: '/login', //Login page
         name: 'Login',
         component: Login,
      },
      {
         path:"/", // home page
         name:"Home",
         component: Home, 
      }
    ]
    
    //Dynamic routing
    export const dynamic_routes = [
       {
           path:"/list", // List page
           name:"List",
           component: List
       },
       {
           path:"/detail", // Detail page
           name:"Detail",
           component: Detail
       },
       {
           path:"/manage", // Management page
           name:"Manage",
           component: Manage
       },
       {
           path:"/admin", // Super management page
           name:"Admin",
           component: Admin
       }
    ]

Now divide all routes into two parts, static routes and dynamic routes_ routes. The pages in static routing can be accessed by all roles. It mainly distinguishes login access and non login access. The processing logic is consistent with that described above.

Dynamic routing stores pages related to role customization. Now continue to look at the interface data of Zhang San below and how to set permissions for him.

import store from "@/store";

export const invisible = [...]; //Static routing

export const dynamic_routes = [...]; //Dynamic routing

const router = createRouter({ //Create routing object
  history: createWebHashHistory(),
  routes,
});

//Dynamically add routes
if(store.state.user != null){ //Get user information from vuex
    //The user is already logged in
    const { permission_list } = store.state.user; // Get permission list from user information
    const allow_routes = dynamic_routes.filter((route)=>{ //Filter allowed routes
      return permission_list.includes(route.name); 
    })
    allow_routes.forEach((route)=>{ // Dynamically add the allowed routes to the route stack
      router.addRoute(route);
    })
}

export default router;

We first get the permission list of the current user from vuex, and then traverse the dynamic routing array dynamic_routes, filter out the routes allowed to access, and finally dynamically add these routes to the routing instance.

In this way, the user can only access the corresponding pages according to the rules in his corresponding permission list. As for the pages he does not have access to, the routing instance does not add the corresponding routing information at all. Therefore, even if the user forcibly enters the path in the browser, unauthorized access cannot be accessed.

The code of dynamically adding routes is best encapsulated separately, because users need to call it when logging in and refreshing the page.

This approach requires maintaining dynamic_routes. When a dynamic route page is added each time, dynamic_ The routes array needs to be added, and the and backend permission needs to be maintained_ The names in the array returned by list are consistent (if they are inconsistent, a name mapping table needs to be established).

In addition, for routes without permission, the page is added to the router. When accessing, it needs to be transferred to the 404 default page.

Add nested sub route

If the form of static routing is as follows, now I want to add the list page to the children of Tabs nested routing.

  const routes = [
  {
    path: '/',  //Label container
    name: 'Tabs',
    component: Tabs,
    children: [{
       path: '', //home page
       name: 'Home',
       component: Home,
    }]
  }
]

export const dynamic_routes = [
  {
      path:"/list", // List page
      name:"List",
      component: List
  }
]

The official route.addroute gives the corresponding configuration to meet such requirements (the code is as follows). Route.addroute accepts two parameters. The first parameter corresponds to the name attribute of the parent route, and the second parameter is the child route information to be added.

   router.addRoute("Tabs", {
        path: "/list",
        name: "List",
        component: List,
 });

The above methods are added to dynamic routing one by one. For projects with many pages and deep page nesting (primary page, secondary page, tertiary page, etc.), you can also refer to the following methods.

The back end returns as follows:

 {
   "home": {
     "id":"100",
     "name":"home",
     "desc":"home page",
     "value":true,
     "children": [],
   }
 }

Determine whether this page has the right to display by judging value. children is the current page or the secondary and tertiary pages under the module. The structure should be the same as home.

At this time, the front end needs to recursively traverse the structure returned by the back end. When it is judged to be false, the corresponding routing page is filtered out.

// Method for generating filter route and menu  
function filterRouter(arr, obj, type) {
  if (Array.isArray(obj)) {
    // Array processing
    obj.forEach(item => {
      handleRouterItem(arr, item, type);
    });
  } else {
    // Object processing
    for (let item in obj) {
      handleRouterItem(arr, obj[item], type);
    }
  }
}

// Process each element node
function handleRouterItem(arr, item, type) {
  // Make sure this page or module is not displayed
  if (item.value === false) {
    if (type === 'menu') {
      assistance(arr, routerMap[item.name]);
    } else {
      assistanceRouter(arr, routerMap[item.name]);
    }
  } else if (!item.isPage && item.childrens && item.childrens.length > 0) {
    filterRouter(arr, item.childrens, type);
  }
}

function assistanceRouter(arr, name, obj) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].name === name) {
      // Set the meta field on the page without permission or delete it directly
      Vue.prototype.$set(arr[i].meta, 'hasRoleAuth', false);
      return true;
    } else {
      if (arr[i].children && arr[i].children.length > 0) {
        if (assistanceRouter(arr[i].children, name, arr[i])) {
          return;
        }
      }
    }
  }
}

// router is the routing structure of all pages, and roleroter is the role permission object returned by the back end
filterRouter(router, roleRouter);

This method is to recursively traverse the permission field at the back end, so as to filter the existing routing structure.

Switch users

Switching user information is a very common function, but the application may cause some problems after switching to different accounts. For example, the user logs in with the super administrator first. Since the super administrator can access all pages, all page routing information will be added to the routing instance.

At this time, the user exits the account and logs in with the account of an ordinary member. Without refreshing the browser, the routing information of all pages is still stored in the routing instance. Even if the current account is only an ordinary member, if he accesses the relevant pages beyond his authority, the routing will jump. This result is not what we want.

There are two solutions.

The first is to refresh the browser and reload it after each account switch. The refreshed routing instance is reconfigured, so this problem can be avoided, but refreshing the page will bring a bad experience.

The second scheme is to clear the routing stack information stored in the routing instance after the user chooses to log out (the code is as follows).

  const router = useRouter(); // Get routing instance
  const logOut = () => { //Logout function
      //Empty the entire routing stack
      const old_routes = router.getRoutes();//Get all routing information
      old_routes.forEach((item) => {
        const name = item.name;//Get routing terms
        router.removeRoute(name); //Remove route
      });
      //Generate a new routing stack
      routes.forEach((route) => {
        router.addRoute(route);
      });
      router.push({ name: "Login" }); //Jump to login page
    };

Content permission control

Page permission control enables different roles to access different pages, but for some projects with smaller granularity, for example, different roles are expected to enter the page, but the page contents required to be seen are different, so permission control of the content is required.

Suppose a background business system has several functions of adding, deleting, modifying and querying. Project requirements the system has three roles: employee, leader and senior leader. Employees do not have the functions of modifying, deleting and publishing. They can only view the list. When an employee enters this page, only the list content is displayed on the page. The buttons of the other three related functions are removed (or pop-up prompt).

Lead role retention list and Publish button. The senior leadership role retains all content on the page.

After we get the picture, we should first analyze the page content as a whole, and classify the page content according to the four dimensions of addition, deletion, query and modification. Use CURD for identification (CURD stands for Create, Update, Retrieve, and Delete, respectively).

The list content in the above figure belongs to query operation, so it is set to R. All users with R permission will display the contents of the list.

Publishing requirements is a new operation. Set this button to be displayed for all users with C permission.

Similarly, the Modify button corresponds to the U permission, and the delete button corresponds to the D permission.

It can be inferred that the permission code of the employee role on this page is R. it can only view the list content and cannot operate.

The permission code corresponding to the leadership role is CR. The corresponding permission code of senior leaders is CURD.

Now, after the user logs in, it is assumed that the data returned by the back-end interface is as follows (save this data to vuex):

 {
  user_id:1,
  user_name:"Zhang San",
  permission_list:{
    "List":"CR", //Permission code
    "Detail":"CURD"  //Permission code
  }
}

In addition to the static route setting page, Zhang San can only access the List page and Detail page. The List page only has the permission to create and add, and the Detail page has all the permissions to add, delete, query and modify. Then, when Zhang San visits the page in the figure above, only the List and publish requirements buttons should be displayed on the page.

What we need to do now is to design a scheme to make the page content easy to be controlled by permission coding as much as possible. First, create a global custom instruction permission. The code is as follows:

import router from './router';
import store from './store';

const app = createApp(App); //Create root instance of vue

app.directive('permission', {
  mounted(el, binding, vnode) {
    const permission = binding.value; // Get permission value
    const page_name = router.currentRoute.value.name; // Get current route name
    const have_permissions = store.state.permission_list[page_name] || ''; // Permissions of the current user
    if (!have_permissions.includes(permission)) {
      el.parentElement.removeChild(el); //Do not have this permission to remove dom elements
    }
  },
});

After the element is mounted, obtain the permission code required by the element through binding.value. Then get the current route name. Through the route name, you can get the permission code of the user on the page in vuex. If the user does not have the permission to access the element, remove the element dom.

Corresponding to the above case, use the v-permission instruction on the page as follows.

<template>
    <div>
      <button v-permission="'U'">modify</button>  <button v-permission="'D'">delete</button>
    </div>
    <p>
      <button v-permission="'C'">Release requirements</button>
    </p>

    <!--List page-->
    <div v-permission="'R'">
     ...
    </div>
</template>

By combining the above template code with user-defined instructions, it is easy to understand the logic of content permission control. First, when developing the front-end page, analyze the page and classify each piece of content according to permission code. For example, the Modify button belongs to U and the delete button belongs to D. And fill in the analysis results with v-permission.

When the page is loaded, all v-permission instructions defined on the page will run. In the user-defined instruction, it will take the permission code owned by the user from vuex, and then combine it with the code set by the element to determine whether the end has display permission. If the permission is not available, remove the element.

Although the analysis process is a little complicated, it is very convenient for each new page to access permission control in the future. You only need to add a v-permission and permission code to each dom element of the new page, and the rest is left to the custom instruction.

For special business scenarios, such as confusion in style and incongruous UI design caused by hiding. At this time, you should judge whether to hide or pop up the prompt of no permission according to the needs of the project. Don't describe too much here.

Last words

Permission control in the front end should be more to optimize the user experience. In addition, it also strengthens a layer of protection for applications. However, it should be noted that the relevant verification of the front end can be cracked by technical means. However, the issue of permission is related to the safety of all data in the software system.

Therefore, in order to ensure the smooth operation of the system, the front and rear ends should do their own authority protection.

Keywords: Vue

Added by sendoh07 on Thu, 04 Nov 2021 22:55:31 +0200