Vue front-end authentication solution, the front and back ends are separated

Technology stack

Vue family bucket front end,.net back end.

Demand analysis

  1. Front-end route authentication, shielding address bar intrusion
  2. Route data is managed by the background, and the front end loads routes asynchronously only according to fixed rules
  3. Permissions are controlled down to every button
  4. Automatic Token Update
  5. Only one account can be logged in to a browser

The front-end solutions

For requirements 1, 2, and 3, an asynchronous route loading scheme is adopted

  1. First write the VUE global route guard
  2. Exclude login routes and authentication-free routes
  3. Request to pull user menu data after login
  4. Process menu and route matching data in VUEX
  5. Routing data processed in VUEX will pass throughaddRoutesAsynchronously push routes
Route. beforeEach((to, from, next) => {// Determine whether the current user has pulled the permission menuif(store. State. The sidebar. UserRouter. Length = = = 0) {/ / no menu pull getMenuRouter (). Then (res = > {let _menu = res.data.Data.ColumnDataList || [];
          // if(res. Data. The data. ColumnDataList. Length > 0) {/ / finishing menu & routing data MIT (store.com"setMenuRouter", _menu); / / push access the router routing list. AddRoutes (store) state. The sidebar. UserRouter); next({... to, replace:true });
          // }
        })
        .catch(err => {
          // console.log(err);
          // Message.error("Server connection failed");
        });
    } else{// If the user has permission, all accessible routes have been generated. If the user has no permission, the 404 page will be displayed automaticallyif (to.path == "/login") {
        next({
          name: "index"
        });
      } else{ next(); }}}else{// Redirect to login or enter no login state pathif (to.path == "/login" || to.meta.auth === 0) {
      next();
    } else {
      next({
        path: "/login"}); }}});Copy the code
Pay attention to

In this case, the routes without authentication are directly written to the index.js in the Router folder, carrying the specified identity through the route meta information

  {
    path: "/err-404",
    name: "err404",
    meta: {
       authentication: false
    },
    component: resolve => require([".. /views/error/404.vue"], resolve)
  },
Copy the code

As mentioned above, routes are generated according to background return menu data according to certain rules. Therefore, some routes that are not menus and require login status are written in router.js under router folder. When processing background return menu data in step 4 above, they are pushed in through addRoutes together with the processed menu route data. There is a certain risk of being invaded by the address bar, but most of the routes here are not very important. If you want to, you can set up a dictionary to work with the background interface to accurately load each route.

// Add enterprise {path:"/join-company",
  name: "join-company",
  component: resolve => require([`@/views/index/join-company.vue`], resolve) 
},
Copy the code

In VUEX, THE allocated menu data is converted into the routing data available to the front end. I do this as follows: When adding a menu, the management system needs to fill in a page address field Url, and the front end gets the background menu data to match the file path loaded by the route according to the Url field. The advantages of one folder for each menu are as follows: This is where you can split JS, CSS, and menu private components

    menu.forEach(item => {
          letrouterItem = { path: item.Url, name: item.Id, meta: { auth: Component.resolve => require([' @/views) component.resolve => require([' @/views${item.Url}/index.vue '], resolve) // Route mapping real view path}; routerBox.push(routerItem); });Copy the code

In terms of how to precisely control each button, I did this by putting the button code in the routing meta and matching it under the current routing to control whether the button on the page was created or not. The menu data returns a multi-level structure, and the subset under each menu is the array of button permission codes under the current menu. I put the buttons under each menu in the routing meta-information meta-auth of this menu. The advantage of this method is that the button permission verification only needs to match the data under each menu routing meta-information, so the verification pool length usually does not exceed 5.

created() {
  this.owner = this.$route.meta.auth.map(item => item.Code);
}
methods: {
    matchingOwner(auth) {
      returnthis.owner.some(item => item === auth); }}Copy the code

Requirement 4 automatically updates the token, which is a simple time judgment, and adds a field in the request header to inform the background to update the token and return the token in the header. The front-end directly updates the token upon receiving the request with the token

// In the axios request interceptorlet token = getSession(auth_code);
    if (token) config.headers.auth = token;
    if(tokenIsExpire (token)) {/ / determine whether need to refresh the JWT config. The headers, refreshtoken =true; } // In the axios response interceptorif (res.headers.auth) {
    setSession(auth_code, res.headers.auth);
  }
Copy the code

The processing of requirement 5 is more troublesome. Cookie or Local is the only way to cross TAB pages. The author does not allow the use of cookies here, so localstorage is adopted. Synchronize account information of multiple pages by reading token data in localstorage through the new page opened. JWT used by the token and front-end MD5 encryption. One thing to note here is that the page switch should synchronize the account information immediately.

The global routing guard modified by Requirement 5 looks like this:

function _AUTH_Window.addeventlistener () {// Check whether the account has changed when switching Windows."visibilitychange".function() {
  let Local_auth = getLocal(auth_code, true);
  let Session_auth = getSession(auth_code);
  if (document.hidden == false&& Local_auth && Local_auth ! = Session_auth) {setSession(auth_code, Local_auth, true); Router.go (0)}}) router.beforeeach ((to, from, next) => {// Determine whether the current user has pulled the permission menuif(store. State. The sidebar. UserRouter. Length = = = 0) {/ / no menu pull getMenuRouter (). Then (res = > {let _menu = res.data.Data.ColumnDataList || [];
          // if(res. Data. The data. ColumnDataList. Length > 0) {/ / finishing menu & routing data MIT (store.com"setMenuRouter", _menu); / / push access the router routing list. AddRoutes (store) state. The sidebar. UserRouter); next({... to, replace:true });
          // }
        })
        .catch(err => {
          // console.log(err);
          // Message.error("Server connection failed");
        });
    } else{// If the user has permission, all accessible routes have been generated. If the user has no permission, the 404 page will be displayed automaticallyif (to.path == "/login") {
        next({
          name: "index"
        });
      } else{ next(); }}}else{// Redirect to login or enter no login state pathif (to.path == "/login" || to.meta.auth === 0) {
      next();
    } else {
      next({
        path: "/login"}); }}}); }Copy the code

Axios request blocker after requirement 5 is like this, because IE cannot use visibilitychange, and attempts to baidu other attributes are not effective, so it does rough processing before the request is sent:

if(Internet Explorer) {setLocal('_ie', Math.random())
    let Local_auth = getLocal(auth_code, true);
    let Session_auth = getSession(auth_code);
    if(Local_auth && Local_auth ! = Session_auth) {setSession(auth_code, Local_auth, true);
      router.go(0)
      return false}}Copy the code

There is one small problem to note here: the first time you open the browser using local, you may get a message indicating that your login has expired

conclusion

After these simple and easy to use processing, a basically meet the needs of the front – end separation authentication scheme was born