The React privilege control scheme practice

Access control is a common function in projects, especially in back-end management projects. Based on actual project requirements, how to achieve access control in React

background

  1. The project was built using UMI
  2. Requirements:

    1. Configure routing permissions according to different role permissions
    2. Control the display effect of the page according to different permissions
    3. Button display hide

Implement page routing permissions

Implement effect: the user without permission has no access to the page: the left menu has no access, and directly enter the URL prompt has no permission

@umijs/plugin-access

https://umijs.org/zh-CN/plugi…


Cooperate with
@umijs/plugin-accessThe plug-in USES

src/access.ts

The SRC /access.ts file is the permission definition file. This file needs to export a method by default. The exported method will be executed when the project is initialized. This method needs to return an object, and each value of the object has a permission defined. See the documentation for detailed introduction.

Plan 1

My requirement is to judge whether the routing authority is available according to the user role. If I follow the document demo, then I need to:

  1. Define whether each role has pageA permission, pageB permission…
  2. Again inaccess.tsOutput object in{canReadPageA: true, canReadPageB: false... }
  3. It is then defined in the routing fileaccess: 'canReadPageA'.access: 'canReadPageB'

This can be done, but there is a lot of code to embed in the Access.ts and route.ts files to define and judge each page that needs permission

So I changed my mind and went to plan 2

Scheme 2

In Access.ts, when the value is a method in the returned object:

  1. The route parameter is the current route information
  2. The method returns a Boolean value at the end

Using this parameter, we can add the information we need to the route

// Routes.ts [{name: 'pageA', path: '/pageA', component: './pageA', access: 'auth', // Routes.ts [{name: 'pageA', path: '/pageA', component: './pageA', access: 'auth', // Routes. }, {name: 'pageB', path: '/pageB', component: './pageB', access: 'auth', roles: ['admin'],// access pageA page only if role is admin},]

I have configured route with two properties:

  1. accessA value ofaccess.tsReturns a key of the object, which is fixed hereauth
  2. rolesDefines the group of roles that can have permissions for this page

Returns an object with a key of auth in access.ts:

// access.ts let hasAuth = (route: any, roleId? Return route.roles {return route.roles {return route.roles; return route.roles; return route.roles; return route.roles; return route.roles; route.roles.includes(roleId) : true; }; export default function access(initialState: { currentUser? : API.CurrentUser | undefined }) { const { currentUser } = initialState || {}; return { auth: (route: any) => hasAuth(route, currentUser? .roleId), }; }

Returns a Boolean value by taking the route message and comparing it to the current user

The advantage of Sceneway 2 compared to Sceneway 1 is that when you add a new page later, you only need to define the access and roles properties of the page in Routes.ts and do not need to change to Access

Realize permission control page display

Implementation effect: the user has a menu entry, enter the page after the display without permission

Plan 1

Ideas:

  1. Use the Access component provided by umi to achieve
  2. The currentUser field can be used to determine if the user has permission code as follows:

    import { Access } from 'umi'; Foo} fallback={<div> </div>}> foo content. </Access>;

    Cons: Need to embed code in the corresponding page

Scheme 2

Ideas:

Implemented through the higher-order component Wrappers

  1. Configure the wrappers property on routes.ts

    // routes.ts
    [
      {
     name: 'pageA',
     path: '/pageA',
     component: './pageA',
     wrappers: ['@/wrappers/authA']
      },
      {
     name: 'pageB',
     path: '/pageB',
     component: './pageB',
     wrappers: ['@/wrappers/authB']
      },
    ]

    This way, when accessing /pageA, the permissions are checked first with @/wrappers/authA

  2. Then, in@/wrappers/authA,
// wrappers/authA import { useModel } from 'umi'; export default (props) => { const { initialState } = useModel('@@initialState'); const { currentUser } = initialState || {}; if (currentUser.authA) { return <div>{ props.children }</div>; } else {return <div> </div>; }}

This determines whether to render the component based on currentUser

The advantages of Option 2 are:

Rather than embed the code in the page component, just configure wrappers in Routes.tx and take care of the validation part by @/wrappers/

The downside, however, is that if you have multiple permissions, such as authA, authB, authC… You’ll need to create multiple authentication files at @/wrappers/

Implement button permissions

Implement effect: the button without permission is not displayed or grayed

The general practice is to judge in the component

// Display button {currentUser.auth? <button disabled={currentUser.auth}> create </button>}

But if you have a lot of permission buttons, you’ll have to write this code multiple times, so wrap the buttons once here

// AuthBtn import React, { useState, useEffect, useRef } from 'react'; import { Button } from 'antd'; const AuthBtn: React.FC<{}> = (props) => { let { authId, children } = props; Let btnIds = ['read', 'edit']; let btnIds = ['read', 'edit']; let hasAuth = btnIds.includes(authId); Return <Button disabled={! hasAuth}>{children}</Button>; }; export default AuthBtn; // index.ts <AuthBtn authId="read">read read-only permission </AuthBtn> <AuthBtn authId="write">write permission </AuthBtn>

The incoming authId needs to be agreed with the background first, and can also be passed in type, loading, etc., according to actual requirements

In this way, ordinary Button with a Button, need to authenticate the use of AuthBtn

conclusion

The above is the recent practice of permission control, to achieve the path, page and button levels to authenticate, each has a corresponding implementation solution, each solution has its own advantages and disadvantages, aimed at more elegant programming! If you have a better idea, let us know in the comments section!