前言
公司的项目是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: {
onl oad: '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>