如何在vite-module-federation微前端间共享Keycloak实例
微前端架构下Keycloak实例共享方案
一、核心思路:分离状态数据与实例方法
Keycloak实例无法存入Redux(破坏不可变性规则),因此将可序列化的身份核心数据(token、userId、过期时间等)存入Redux实现跨微前端同步,将Keycloak实例的关键方法通过模块联邦封装共享,既保证数据时效性,又满足子应用对实例方法的需求。
二、具体实现步骤
1. 共享可序列化身份数据到Redux
在HostApp初始化Keycloak后,提取可序列化的关键身份数据同步到StoreMF,同时处理令牌自动刷新逻辑:
// store-mf/src/slices/authSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { token: null, refreshToken: null, userId: null, tokenExpiry: null, refreshTokenExpiry: null, }; const authSlice = createSlice({ name: 'auth', initialState, reducers: { setAuthData: (state, action) => { Object.assign(state, action.payload); }, clearAuthData: (state) => { Object.assign(state, initialState); }, }, }); export const { setAuthData, clearAuthData } = authSlice.actions; export default authSlice.reducer;
HostApp初始化并同步数据:
// HostApp.jsx import { useDispatch } from 'react-redux'; import { setAuthData, clearAuthData } from 'store-mf/authSlice'; import Keycloak from 'keycloak-js'; const keycloak = new Keycloak('/keycloak.json'); const HostApp = () => { const dispatch = useDispatch(); useEffect(() => { keycloak.init({ onLoad: 'login-required' }).then(authenticated => { if (authenticated) { syncAuthDataToRedux(); // 每分钟检查令牌有效性,自动刷新并同步 const refreshInterval = setInterval(() => { keycloak.updateToken(30).then(refreshed => { if (refreshed) syncAuthDataToRedux(); }).catch(() => { dispatch(clearAuthData()); clearInterval(refreshInterval); }); }, 60000); return () => clearInterval(refreshInterval); } }); }, [dispatch]); const syncAuthDataToRedux = () => { dispatch(setAuthData({ token: keycloak.token, refreshToken: keycloak.refreshToken, userId: keycloak.subject, tokenExpiry: keycloak.tokenParsed?.exp, refreshTokenExpiry: keycloak.refreshTokenParsed?.exp, })); }; return ( <StoreProvider store={StoreMF.store}> <HeaderMF /> <FooterMF /> </StoreProvider> ); };
2. 通过模块联邦共享Keycloak实例方法
将Keycloak实例封装成独立联邦模块,仅暴露必要方法(避免直接共享整个实例):
// auth-mf/src/keycloakService.js import Keycloak from 'keycloak-js'; let keycloakInstance = null; export const initKeycloak = (config) => { keycloakInstance = new Keycloak(config); return keycloakInstance.init({ onLoad: 'login-required' }); }; // 仅暴露业务需要的方法,最小权限原则 export const getAuthMethods = () => { if (!keycloakInstance) { throw new Error('Keycloak未初始化,请先调用initKeycloak'); } return { logout: () => keycloakInstance.logout(), updateToken: (minValidity) => keycloakInstance.updateToken(minValidity), isAuthenticated: () => keycloakInstance.authenticated, }; };
在vite配置中暴露该模块:
// auth-mf/vite.config.js import { defineConfig } from 'vite'; import federation from '@originjs/vite-plugin-federation'; export default defineConfig({ plugins: [ federation({ name: 'authMF', filename: 'remoteEntry.js', exposes: { './authService': './src/keycloakService.js', }, shared: ['react', 'react-dom', 'keycloak-js'], }), ], });
子应用通过模块联邦导入方法使用:
// HeaderMF.jsx import { getAuthMethods } from 'authMF/authService'; const HeaderMF = () => { const handleLogout = () => { const { logout } = getAuthMethods(); logout(); }; return ( <header> <button onClick={handleLogout}>退出登录</button> </header> ); };
3. 子应用独立API请求的身份认证
子应用通过RTK Query发起请求时,自动从Redux获取token注入请求头:
// 子应用/src/services/apiSlice.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; const baseQuery = fetchBaseQuery({ baseUrl: '/api', prepareHeaders: (headers, { getState }) => { const token = getState().auth.token; if (token) { headers.set('Authorization', `Bearer ${token}`); } return headers; }, }); export const apiSlice = createApi({ baseQuery, endpoints: (builder) => ({ getUserInfo: builder.query({ query: (userId) => `/users/${userId}`, }), }), }); export const { useGetUserInfoQuery } = apiSlice;
三、安全性保障
- 最小权限控制:仅暴露Keycloak实例的必要方法,禁止子应用直接操作完整实例,避免恶意篡改。
- 令牌自动刷新:HostApp统一管理令牌生命周期,自动刷新并同步到Redux,确保子应用获取的token始终有效。
- Redux状态合规:仅存储可序列化的原始数据,严格遵守Redux不可变性规则,避免状态污染。
- 请求头自动注入:子应用API请求无需手动管理token,通过RTK Query自动从Redux取数注入,减少人为错误。
- 依赖版本统一:将Keycloak作为模块联邦共享依赖,确保所有微前端使用同一版本,避免兼容性与安全漏洞问题。
内容的提问来源于stack exchange,提问作者Patryk Gajewski
相关产品推荐
相关产品推荐

