vue-element-admin интегрирует Keycloak для реализации унифицированной аутентификации и контроля разрешений.

Vue.js

vue-element-adminЭто бэкенд-интерфейсное решение с очень большим количеством звезд на github, основанное наvueа такжеelement-uiвыполнить. Vue сам по себе прост в использовании, а компоненты element-ui богаты.Даже бэкенд-разработчики могут использовать vue-element-admin для быстрого создания хорошего опыта управления. Однако при наличии в компании нескольких серверных систем и непосредственном использовании встроенных функций входа и аутентификации vue-element-admin цель унифицированного управления пользователями и разрешениями не может быть достигнута. единый вход в систему SSO, аутентификация и разрешения не могут быть достигнуты.Контроль особенно важен и удобен. В этой статье объясняется, как интегрировать vue-element-adminKeycloakметод для достижения унифицированного входа в систему SSO, аутентификации и управления разрешениями. Если вы мало что знаете о Keycloak, вы можете обратиться к одной из моих предыдущих статей.Keycloak Краткое руководство, сначала иметь предварительное понимание основных понятий, я полагаю, что последующее понимание этой статьи будет полезно.

Аутентификация Vue-element-admin, анализ основного кода управления разрешениями

Поскольку vue-element-admin должен быть интегрирован с Keycloak, необходимо сначала понять, как реализованы функции, связанные с входом в систему, аутентификацией и контролем разрешений самого vue-element-admin. Основной код в основном находится в следующих файлах:

  • src/permission.js:противvue-routerКонфигурация глобальной системы навигации, основная логика аутентификации и контроля разрешений находятся в этом файле.
  • src/router/index.js:vue-routerСвязанная конфигурация маршрутизации, в которойasyncRoutesС участиемrolesСвязанная конфигурация динамической маршрутизации
  • src/modules/permission.js:согласно сrolesКонтролируйте разрешения и создавайте динамические маршрутыvuexСвязанные операции
  • src/modules/user.js: вход и выход, информация о пользователе, токен и т. д.vuexСвязанные операции
  • src/api/user.js: операции API для входа и выхода, информация о пользователе.
  • src/views/login/index.vue:страница авторизации
  • src/layout/components/Navbar.vue: Компонент, в котором находится запись выхода из системы.

Далее мы проанализируем некоторые основные коды в вышеуказанных файлах.

src/permission.jsАнализ основного кода

Большая часть кода относительно проста.Токен получается из куки.Конечно, хранилище токена тоже можно настроить, например, в localStorage.Сосредоточьтесь на коде в части динамической маршрутизации:

  // determine whether the user has obtained his permission roles through getInfo
  const hasRoles = store.getters.roles && store.getters.roles.length > 0
  if (hasRoles) {
    next()
  } else {
    try {
      // get user info
      // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
      const { roles } = await store.dispatch('user/getInfo')

      // generate accessible routes map based on roles
      const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

      // dynamically add accessible routes
      router.addRoutes(accessRoutes)

      // hack method to ensure that addRoutes is complete
      // set the replace: true, so the navigation will not leave a history record
      next({ ...to, replace: true })
    } catch (error) {
      // remove token and go to login page to re-login
      await store.dispatch('user/resetToken')
      Message.error(error || 'Has Error')
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }

передачаvuexсерединаuser/getInfoПолучите роль пользователя пользователя и динамически сгенерируйте соответствующий маршрут в соответствии с ролью пользователя.

src/router/index.jsАнализ основного кода

  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/page',
    alwaysShow: true, // will always show the root menu
    name: 'Permission',
    meta: {
      title: 'Permission',
      icon: 'lock',
      roles: ['admin', 'editor'] // you can set roles in root nav
    },
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'PagePermission',
        meta: {
          title: 'Page Permission',
          roles: ['admin'] // or you can only set roles in sub nav
        }
      },
      {
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        name: 'DirectivePermission',
        meta: {
          title: 'Directive Permission'
          // if do not set roles, means: this page does not require permission
        }
      },
      {
        path: 'role',
        component: () => import('@/views/permission/role'),
        name: 'RolePermission',
        meta: {
          title: 'Role Permission',
          roles: ['admin']
        }
      }
    ]
  }

вышеasyncRoutesЧасть определения в

src/modules/permission.jsАнализ основного кода

в основном на основеrolesтак же какasyncRoutesМаршруты, определенные для генерации маршрутов с разрешениями доступа

  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }

src/modules/user.jsАнализ основного кода

login action

Сохраните токен после вызова API для успешного входа в систему.

  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  }

getInfo action

Получите информацию о пользователе и сохраните ее, в основном роли, имя, аватар, введение, если вам нужна дополнительная информация, вы можете развернуть ее здесь

  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response

        if (!data) {
          reject('Verification failed, please Login again.')
        }

        const { roles, name, avatar, introduction } = data

        // roles must be a non-empty array
        if (!roles || roles.length <= 0) {
          reject('getInfo: roles must be a non-null array!')
        }

        commit('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  }

logout action

Выйдите из системы и очистите сохраненные токены, роли и т. д.

  logout({ commit, state, dispatch }) {
    return new Promise((resolve, reject) => {
      logout(state.token).then(() => {
        commit('SET_TOKEN', '')
        commit('SET_ROLES', [])
        removeToken()
        resetRouter()

        // reset visited views and cached views
        // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
        dispatch('tagsView/delAllViews', null, { root: true })

        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  }

src/views/login/index.vueАнализ основного кода

передачаvuexсерединаuser/loginдействие для входа в систему и сохранения операций с токенами

  this.$refs.loginForm.validate(valid => {
    if (valid) {
      this.loading = true
      this.$store.dispatch('user/login', this.loginForm)
        .then(() => {
          this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
          this.loading = false
        })
        .catch(() => {
          this.loading = false
        })
    } else {
      console.log('error submit!!')
      return false
    }
  })

src/layout/components/Navbar.vueанализ кода выхода

передачаvuexсерединаuser/logoutдействие, чтобы выйти из системы, очистить токен и перейти на страницу входа

  async logout() {
    await this.$store.dispatch('user/logout')
    this.$router.push(`/login?redirect=${this.$route.fullPath}`)
  }

vue-element-admin объединяет идеи интеграции с Keycloak

С помощью приведенных выше файлов и анализа кода, связанного с аутентификацией и контролем разрешений, мы будем знать, что нужно изменить для интеграции Keycloak. На самом деле, наиболее важными вещами, которые необходимо сделать, являются следующие 3 вещи:

  • Аутентификация передается Keycloak.Если аутентификация не удалась, пользователь входит непосредственно на страницу входа в Keycloak.

  • Токен и информация о пользователе (имя, роли и т. д.) изменены с исходного способа вызова независимого API на прямое получение от Keycloak.

  • Выход пользователя изменен на использование Keycloak для единого выхода.

Проще говоря, вход в систему, получение информации для аутентификации пользователя и выход из системы передаются Keycloak.

Подготовка конфигурации бэкенда Keycloak

Прежде чем официально начать изменять соответствующие файлы, мы должны сначала создать соответствующий клиент, роли, пользователей и другие ресурсы в фоновом режиме управления Keycloak, чтобы их можно было использовать при интеграции Keycloak для последующего vue-element-admin.

Создать клиента

keycloak-vue-element-admin-client-1

Создайте 2 персонажей

keycloak-vue-element-admin-role

Создайте 2 пользователей

Создать пользователя-администратора

Создайте пользователя-администратора и добавьте 2 атрибута: аватар, введение, информация о пользователе по умолчанию в Keycloak относительно мала, вам нужно расширить информацию о пользователе, которую можно добавить в атрибуты.

keycloak-vue-element-admin-user-admin-1

Добавить роль администратора для пользователя-администратора

keycloak-vue-element-admin-user-admin-2

Создать пользовательский редактор

Создайте пользователя редактора и добавьте 2 атрибута: аватар, введение

keycloak-vue-element-admin-user-editor-1

Добавить роль редактора для пользователя-редактора

keycloak-vue-element-admin-user-editor-2

Клиент устанавливает сопоставление атрибутов

После настройки сопоставителей атрибутов idtoken будет содержать соответствующую информацию об атрибутах, чтобы клиент js мог напрямую получать информацию о пользователе.

keycloak-vue-element-admin-client-2

Подробное объяснение кода Keycloak, интегрированного в vue-element-admin.

После приведенного выше анализа мы знаем, что части входа в систему, получения информации для аутентификации пользователя и выхода из системы необходимо изменить и передать Keycloak для обработки. Мы изменим следующие файлы:

  • src/store/modules/user.js:БудуvuexДействия, связанные с входом в систему, получением информации о пользователе и выходом из системы, изменены для управления через Keycloak.
  • src/main.js: Добавить интеграцию инициализации Keycloak, передать часть аутентификации и входа в Keycloak.
  • src/permission.js:ПолучатьrolesВместо этого частично получен из Keycloak
  • src/layout/components/Navbar.vue: Логика выхода изменена, чтобы вызвать выход Keycloak.

Ниже будет показан конкретный код этих 4 изменений файла.

src/store/modules/user.jsизменения кода

Не изменяйте напрямую исходное действие vuex, добавьте 3 действия, связанные с keycloak

Добавить действие keycloakLogin

Действие keycloakLogin в основном заключается в установке токена после успешного входа в систему Keycloak.

  keycloakLogin({ commit }, accessToken) {
    return new Promise((resolve, reject) => {
      commit('SET_TOKEN', accessToken)
      setToken(accessToken)
      resolve()
    })
  }

Добавить действие getKeycloakInfo

Действие getKeycloakInfo используется для получения информации о пользователе от Keycloak.Этот пример включает четыре сведения о пользователе: роли, имя, аватар и введение.

  getKeycloakInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      if (!Vue.prototype.$keycloak) {
        reject('keycloak not init')
      }

      if (!Vue.prototype.$keycloak.authenticated) {
        reject('Verification failed, please Login again.')
      }

      const roles = Vue.prototype.$keycloak.realmAccess.roles
      const name = Vue.prototype.$keycloak.idTokenParsed.preferred_username
      const avatar = Vue.prototype.$keycloak.idTokenParsed.avatar
      const introduction = Vue.prototype.$keycloak.idTokenParsed.introduction

      // roles must be a non-empty array
      if (!roles || roles.length <= 0) {
        reject('getKeycloakInfo: roles must be a non-null array!')
      }

      // you can also use the method loadUserProfile() to get user attributes
      // Vue.prototype.$keycloak.loadUserProfile().then(profile => {
      //   let avatar = profile.attributes.avatar[0]
      //   let introduction = profile.attributes.introduction[0]
      // })

      const data = {
        roles,
        name,
        avatar,
        introduction
      }

      commit('SET_ROLES', roles)
      commit('SET_NAME', name)
      commit('SET_AVATAR', avatar)
      commit('SET_INTRODUCTION', introduction)
      resolve(data)
    })
  }

Следует отметить, что пользовательские атрибуты: аватар и введение получаются непосредственно из idTokenParsed keycloak, потому что сопоставления атрибутов устанавливаются в фоновом режиме Keycloak.Если этот параметр не сделан, вы можете передать метод loadUserProfile() keycloak для получения пользовательских атрибутов

Добавить действие keycloakLogout

Действие keycloakLogout заключается в выходе из системы через Keycloak. После успеха локально сохраненная информация будет удалена.

  keycloakLogout({ commit, state }) {
    return new Promise((resolve, reject) => {
      Vue.prototype.$keycloak.logout().then(() => {
        removeToken() // must remove  token  first
        resetRouter()
        commit('RESET_STATE')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  }

src/main.jsизменения кода

Входной файл main.js в основном предназначен для инициализации и интеграции с Keycloak, который вызывается после прохождения аутентификации Keycloak.user/keycloakLoginжетон сохранения действия

// keycloak init options
const initOptions = {
  url: process.env.VUE_APP_KEYCLOAK_OPTIONS_URL,
  realm: process.env.VUE_APP_KEYCLOAK_OPTIONS_REALM,
  clientId: process.env.VUE_APP_KEYCLOAK_OPTIONS_CLIENTID,
  onLoad: process.env.VUE_APP_KEYCLOAK_OPTIONS_ONLOAD
}

const keycloak = Keycloak(initOptions)

keycloak.init({ onLoad: initOptions.onLoad }).then(async authenticated => {
  if (!authenticated) {
    window.location.reload()
    return
  } else {
    Vue.prototype.$keycloak = keycloak
    await store.dispatch('user/keycloakLogin', keycloak.token)
    console.log('Authenticated', keycloak)
  }

  setInterval(() => {
    keycloak.updateToken(70).then((refreshed) => {
      if (refreshed) {
        console.log('Token refreshed')
      } else {
        console.log('Token not refreshed, valid for ' +
          Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds')
      }
    }).catch(error => {
      console.log('Failed to refresh token', error)
    })
  }, 60000)

  new Vue({
    el: '#app',
    router,
    store,
    render: h => h(App)
  })
}).catch(error => {
  console.log('Authenticated Failed', error)
})

src/permission.jsизменения кода

вместо этого вызывается roles getuser/getKeycloakInfoдействие получено от Keycloak

const { roles } = await store.dispatch('user/getKeycloakInfo')

src/layout/components/Navbar.vueизменения кода

Логика выхода изменена на вызовuser/keycloakLogoutДействие выполняет унифицированную обработку выхода из системы через Keycloak и удаляет логику перехода к входу в систему, которая поставляется с vue-element-admin.

async logout() {
  await this.$store.dispatch('user/keycloakLogout')
  // this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}

Демонстрация эффекта после интеграции Keycloak vue-element-admin

местныйnpm run devПосле запуска страницы вы впервые попадете на страницу входа в Keycloak.После входа в систему через пользователей администратора и редактора, созданных выше, эффект будет следующим

эффект входа пользователя с правами администратора

После того, как пользователь-администратор войдет в систему, на панели инструментов будет отображаться полная диаграмма, а в меню «Разрешения» можно увидеть три подменю.

keycloak-vue-element-admin-result-1

Эффект входа пользователя в редактор

После редактора журналов пользователей панель инструментов показывает только базовую информацию и можно увидеть только под пунктом разрешения подменю 1

keycloak-vue-element-admin-result-2

Суммировать

В этой статье сначала анализируется исходная логика проверки подлинности и контроля разрешений vue-element-admin, затем дается идея интеграции с Keycloak и, наконец, показывается метод интеграции vue-element-admin и Keycloak через определенный код. Для других интерфейсных приложений идея интеграции с Keycloak такая же, то есть части входа в систему, получения информации о пользователе и выхода из системы передаются Keycloak для унифицированной обработки.

Комплексный адрес проекта:vue-element-admin-keycloak