vben集成keycloak

前言

公司的项目是vben admin框架需要集成keycloak,那keycloak大家应该都不陌生了,就是统一认证的一个系统简称IDS。之前用过cas,并重构过cas的前端界面,所以对此也是比较熟悉。

在keycloak官网有个例子,不过他是相对于vue2时候的,现在大家都在用vue3 vite,所以生成vue事例不一样了,vue中我们都知道new Vue()中有个render ,可以在创建的element上添加props属性。可以在vue3中是creatApp(),这个就比较懵了,没有地方写props,那我的keycloak应该写在哪儿呢?

好了,废话不多说,直接上代码

1. 首先下载:yarn add @dsb-norge/vue-keycloak-js
2. 新增文件:src/keyCloak.ts
keyCloak.ts
import type { App } from 'vue';
import keycloak from '@dsb-norge/vue-keycloak-js';

export function setupKeyCloak(app: App<Element>, bootstrap) {
  app.use(keycloak, {
    init: {
      onLoad: 'login-required',
      checkLoginIframe: true, //防止登陆后重复刷新
    },
    config: {
      url: 'http://60.215.255.22:8088/auth', // 你的keycloak地址
      realm: 'OM',
      clientId: 'om-cli',
    },
    onReady: (keycloak) => {
      app.config.globalProperties.$keycloak = keycloak;
      console.log(keycloak);
      keycloak.loadUserProfile().success((data) => {
        console.log(data);
        bootstrap(keycloak.token);
      });
    },
  });
}

3. 修改:src/main.ts
main.ts
import 'virtual:windi-base.css';
import 'virtual:windi-components.css';
import '/@/design/index.less';
import 'virtual:windi-utilities.css';
// Register icon sprite
import 'virtual:svg-icons-register';
import App from './App.vue';
import { createApp } from 'vue';
import { initAppConfigStore } from '/@/logics/initAppConfig';
import { setupErrorHandle } from '/@/logics/error-handle';
import { router, setupRouter } from '/@/router';
import { setupRouterGuard } from '/@/router/guard';
import { setupStore } from '/@/store';
import { setupGlobDirectives } from '/@/directives';
import { setupI18n } from '/@/locales/setupI18n';
import { registerGlobComp } from '/@/components/registerGlobComp';
import { setupKeyCloak } from '/@/keyCloak';

// 配置 keycloak
import { useUserStore } from '/@/store/modules/user';
const app = createApp(App);
setupKeyCloak(app, bootstrap);

async function bootstrap(token) {
  // const app = createApp(App);

  // Configure store
  // 配置 store
  setupStore(app);

  // Initialize internal system configuration
  // 初始化内部系统配置
  initAppConfigStore();

  // Register global components
  // 注册全局组件
  registerGlobComp(app);

  // Multilingual configuration
  // 多语言配置
  // Asynchronous case: language files may be obtained from the server side
  // 异步案例:语言文件可能从服务器端获取
  await setupI18n(app);

  // Configure routing
  // 配置路由
  setupRouter(app);

  // router-guard
  // 路由守卫
  setupRouterGuard(router);

  // Register global directive
  // 注册全局指令
  setupGlobDirectives(app);

  // Configure global error handling
  // 配置全局错误处理
  setupErrorHandle(app);

  // https://next.router.vuejs.org/api/#isready
  // await router.isReady();

  // 登陆方法修改
  const userStore = useUserStore();
  userStore.loginX(token);

  app.mount('#app');
}

4. 添加loginX:src/store/modules/user.ts
5. 添加logoutX:src/store/modules/user.ts
6. 修改confirmLoginOut:src/store/modules/user.ts
点击查看代码
async loginX(token): Promise<GetUserInfoModel | null> {
      try {
        this.setToken(token);
        return this.afterLoginAction(true);
      } catch (error) {
        return Promise.reject(error);
      }
    },
async logoutX(goLogin = false) {
      this.setToken(undefined);
      this.setUserInfo(null);
      console.log('退出成功');
      goLogin && router.replace(PageEnum.BASE_HOME);
      // goLogin && router.push(PageEnum.BASE_LOGIN);
    },

    /**
     * @description: Confirm before logging out
     */
    confirmLoginOut(globalProperties) {
      const { createConfirm } = useMessage();
      const { t } = useI18n();
      createConfirm({
        iconType: 'warning',
        title: () => h('span', t('sys.app.logoutTip')),
        content: () => h('span', t('sys.app.logoutMessage')),
        onOk: async () => {
          await this.logoutX(true);
          await globalProperties.$keycloak.logout();
        },
      });
    },
7. 修改界面的退出登录:src/layouts/default/header/components/user-dropdown/index.vue
点击查看代码
<template>
  <Dropdown placement="bottomLeft" :overlayClassName="`${prefixCls}-dropdown-overlay`">
    <span :class="[prefixCls, `${prefixCls}--${theme}`]" class="flex">
      <img :class="`${prefixCls}__header`" :src="getUserInfo.avatar" />
      <span :class="`${prefixCls}__info hidden md:block`">
        <span :class="`${prefixCls}__name  `" class="truncate">
          {{ getUserInfo.realName }}
        </span>
      </span>
    </span>

    <template #overlay>
      <Menu @click="handleMenuClick">
        <MenuItem
          key="doc"
          :text="t('layout.header.dropdownItemDoc')"
          icon="ion:document-text-outline"
          v-if="getShowDoc"
        />
        <MenuDivider v-if="getShowDoc" />
        <MenuItem
          v-if="getUseLockPage"
          key="lock"
          :text="t('layout.header.tooltipLock')"
          icon="ion:lock-closed-outline"
        />
        <MenuItem
          key="logout"
          :text="t('layout.header.dropdownItemLoginOut')"
          icon="ion:power-outline"
        />
      </Menu>
    </template>
  </Dropdown>
  <LockAction @register="register" />
</template>
<script lang="ts">
  // components
  import { Dropdown, Menu } from 'ant-design-vue';
  import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';

  import { defineComponent, computed, getCurrentInstance } from 'vue';

  import { DOC_URL } from '/@/settings/siteSetting';

  import { useUserStore } from '/@/store/modules/user';
  import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
  import { useI18n } from '/@/hooks/web/useI18n';
  import { useDesign } from '/@/hooks/web/useDesign';
  import { useModal } from '/@/components/Modal';

  import headerImg from '/@/assets/images/header.jpg';
  import { propTypes } from '/@/utils/propTypes';
  import { openWindow } from '/@/utils';

  import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';

  type MenuEvent = 'logout' | 'doc' | 'lock';

  export default defineComponent({
    name: 'UserDropdown',
    components: {
      Dropdown,
      Menu,
      MenuItem: createAsyncComponent(() => import('./DropMenuItem.vue')),
      MenuDivider: Menu.Divider,
      LockAction: createAsyncComponent(() => import('../lock/LockModal.vue')),
    },
    props: {
      theme: propTypes.oneOf(['dark', 'light']),
    },
    setup() {
      const internalInstance = getCurrentInstance();
      const { prefixCls } = useDesign('header-user-dropdown');
      const { t } = useI18n();
      const { getShowDoc, getUseLockPage } = useHeaderSetting();
      const userStore = useUserStore();

      const getUserInfo = computed(() => {
        const { realName = '', avatar, desc } = userStore.getUserInfo || {};
        return { realName, avatar: avatar || headerImg, desc };
      });

      const [register, { openModal }] = useModal();

      function handleLock() {
        openModal(true);
      }

      //  d
      function handleLoginOut() {
        userStore.confirmLoginOut(internalInstance?.appContext.config.globalProperties);
      }

      // open doc
      function openDoc() {
        openWindow(DOC_URL);
      }

      function handleMenuClick(e: MenuInfo) {
        switch (e.key as MenuEvent) {
          case 'logout':
            handleLoginOut();
            break;
          case 'doc':
            openDoc();
            break;
          case 'lock':
            handleLock();
            break;
        }
      }

      return {
        prefixCls,
        t,
        getUserInfo,
        handleMenuClick,
        getShowDoc,
        register,
        getUseLockPage,
      };
    },
  });
</script>
<style lang="less">
  @prefix-cls: ~'@{namespace}-header-user-dropdown';

  .@{prefix-cls} {
    height: @header-height;
    padding: 0 0 0 10px;
    padding-right: 10px;
    overflow: hidden;
    font-size: 12px;
    cursor: pointer;
    align-items: center;

    img {
      width: 24px;
      height: 24px;
      margin-right: 12px;
    }

    &__header {
      border-radius: 50%;
    }

    &__name {
      font-size: 14px;
    }

    &--dark {
      &:hover {
        background-color: @header-dark-bg-hover-color;
      }
    }

    &--light {
      &:hover {
        background-color: @header-light-bg-hover-color;
      }

      .@{prefix-cls}__name {
        color: @text-color-base;
      }

      .@{prefix-cls}__desc {
        color: @header-light-desc-color;
      }
    }

    &-dropdown-overlay {
      .ant-dropdown-menu-item {
        min-width: 160px;
      }
    }
  }
</style>

posted @ 2022-10-12 10:02  大耳朵小虎  阅读(420)  评论(1编辑  收藏  举报