20
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Vue】メニュー周りの権限制御を行う

Last updated at Posted at 2019-08-26

概要

VueなどでSPAを構築する場合、大体こんな感じのメニューがあると思います。
 2019-08-24 13.39.25.png

クリックされた項目でURLをルーティングして内容を変化させるやつですね。

権限制御を行う

このメニューで、以下のような権限制御を行う場合の例をご紹介します。
(※バックエンドについては今回省略します。)

  • ユーザーのロールに設定された権限によりメニューの項目を出し分けたい
  • 使用する権限を持っていない場合、項目は非表示とする
  • URL直打ちでも開けないようにしたい

環境

テーブル

テーブルはこんな感じです
 2019-08-26 21.32.16.png
ユーザーはロールを1つ持ち、ロールは権限を複数持つ構成となっています。

ユーザーテーブルのレコード例

ユーザーコード パスワード ロールコード
admin ******* admin
user1 ******* writer
user2 ******* readonly

ロール権限のレコード例

ロールコード 権限
admin menu1
admin menu2
admin menu3
writer menu1
writer menu2
readonly menu1

権限を取得

ストア

  • ユーザーに紐付いた権限を保持します。
action-types.js
export const GET_MENU_FLGS = "GET_MENU_FLGS";

export const ACTION = {
  GET_MENU_FLGS
};
mutation-types.js
export const UPDATE = "UPDATE";

export const MUTATION = {
  UPDATE
};
menu-store.js
import { MUTATION } from "./mutation-types";
import { ACTION } from "./action-types";
import axios from "axios";

const state = {
  //権限
  menuFlgs: {}
};

const getters = {
  menuFlgs: state => {
    return state.menuFlgs;
  }
};

const actions = {
  //ログインユーザーのメニュー使用権限を取得します。
  async [ACTION.GET_MENU_FLGS]({ commit }) {
    await axios
      .get("menus")
      .then(res => {
        commit(MUTATION.UPDATE, {
          menuFlgs: res.data
        });
      })
      .catch(err => {
        throw err;
      });
  }
};

const mutations = {
  [MUTATION.UPDATE](state, { menuFlgs }) {
    state.menuFlgs = menuFlgs;
  }
};

export default {
  state: state,
  getters: getters,
  actions: actions,
  mutations: mutations
};

権限のJSONはこのようなイメージです。

menuFlgs: [
  {
    menu1: true
  },
  {
    menu2: true
  },
  {
    menu3: true
  }
];

VueRouter

  • ①URLごとに権限を持っているかを判定します
    • 権限がない場合は遷移をキャンセルします
  • ②ストアに権限が保持されていない場合、取得します
router.js
import Vue from "vue";
import Router from "vue-router";
import multiguard from "vue-router-multiguard";
import store from "/store/store.js";
import { GET_MENU_FLGS } from "/store/menu/action-types";

Vue.use(Router);

/* *
 * ①ナビゲーションガード
 * */
const menu1Guard = (to, from, next) => {
  if (store.getters.menuFlgs.menu1) {
    next();
  } else {
    next(false);
  }
};

const menu2Guard = (to, from, next) => {
  if (store.getters.menuFlgs.menu2) {
    next();
  } else {
    next(false);
  }
};

const menu3Guard = (to, from, next) => {
  if (store.getters.menuFlgs.menu3) {
    next();
  } else {
    next(false);
  }
};

/* *
 * ルート定義
 * */
const router = new Router({
  routes: [
    {
      path: "/menu1",
      //①
      beforeEnter: multiguard([menu1Guard]),
      component: () => import("@/components/pages/menu1/menu1Template")
    },
    {
      path: "/menu2",
      //①
      beforeEnter: multiguard([menu2Guard]),
      component: () => import("@/components/pages/menu2/menu2Template")
    },
    {
      path: "/menu3",
      //ナビゲーションガードを複数設定することもできます。
      beforeEnter: multiguard([menu1Guard, menu2Guard, menu3Guard]),
      component: () => import("@/components/pages/menu3/menu3Template")
    }
  ]
});


/* *
 * ②権限を取得します。
 * */
router.beforeEach(async (to, from, next) => {
  if (!Object.keys(store.getters.menuFlgs).length) {
    await store.dispatch(GET_MENU_FLGS);
  }

  next();
});

export default router;


メニュー表示

  • ①メニューを配列で定義しておきます。
  • ②ストアの権限から、使用可能なメニューのみ表示します
<template>
  <v-list>
    <v-list-tile v-for="menu in availableMenus" :key="menu.id">
      <router-link :to="menu.url">{{menu.title}}</router-link>
    </v-list-tile>
  </v-list>
</template>

<script>
export default {
  data() {
    return {
      //①メニューマスタ
      menuMaster: [
        {
          id: 1,
          title: "メニュー1",
          url: "/menu1"
        },
        {
          id: 2,
          title: "メニュー2",
          url: "/menu2"
        },
        {
          id: 3,
          title: "メニュー3",
          url: "/menu3"
        }
      ]
    };
  },
  computed: {
    //②使用可能なメニューで絞り込みます
    availableMenus() {
      let available = [];
      for (const menu of this.menuMaster) {
        if (this.isAvailable(menu.url)) {
          available.push(menu);
        }
      }
      return available;
    }
  },
  methods: {
    //ログインユーザーがメニューの使用権限を持っているかを判定します。
    isAvailable(url) {
      const menuName = url.slice(1);
      return this.$store.getters.menuFlgs[menuName];
    }
  }
};
</script>

まとめ

  • Vue Router Multiguardを使うとナビゲーションガードが扱いやすくなりました
  • 権限をどのタイミングで取得するかは賛否両論ありそうです

20
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
23

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?