多/不同令牌下微前端(MFE)的授权方案咨询
微前端MFE跨Shell授权实现方案(Angular + Keycloak)
问题概述
基于Angular Module Federation构建的微前端架构中,单个MFE需集成至两个独立Shell应用。Shell与MFE各自通过Keycloak授权,拥有独立的受保护API。当前核心问题是Shell的访问令牌无法被MFE的API识别,导致集成后MFE功能异常;同时需支持MFE被直接访问时的独立授权流程。
核心需求
- 从Shell访问MFE时,MFE可使用有效的令牌调用自身API
- MFE独立访问时,能自行完成Keycloak登录授权
- 两个Shell应用均需实现上述集成效果
解决方案
1. Keycloak客户端配置调整(关键)
通过配置Keycloak客户端的受众与权限,让Shell令牌可被MFE的API接受:
- 分别创建Shell1、Shell2、MFE三个独立Keycloak客户端
- Shell客户端配置:
- 在客户端的
Scope设置中,添加MFE客户端的ID到Allowed Scopes,确保Shell登录时请求的令牌包含aud:mfe-client-id受众 - 启用
Authorization Code Flow with PKCE,保障令牌获取安全
- 在客户端的
- MFE客户端配置:
- 在
Valid Audiences中添加自身ID以及两个Shell的客户端ID,允许API接受来自Shell的令牌 - 配置
Valid Redirect URIs,包含MFE自身域名与两个Shell的域名,支持独立登录与Shell跳转场景 - 开启
Allow CORS,添加Shell与MFE的域名到Web Origins列表,解决跨域问题
- 在
2. Angular端共享授权上下文
利用Module Federation的共享能力,在Shell与MFE间同步Keycloak会话:
- 在Shell与MFE的
webpack.config.js中,共享@angular-keycloak/core与keycloak-js模块,确保两者使用同一套Keycloak实例:// webpack.config.js (Shell/MFE) module.exports = { plugins: [ new ModuleFederationPlugin({ shared: { "@angular/core": { singleton: true, strictVersion: true }, "@angular-keycloak/core": { singleton: true }, "keycloak-js": { singleton: true } } }) ] }; - Shell初始化Keycloak后,直接加载MFE模块即可自动复用当前会话:
// Shell的app.component.ts import { KeycloakService } from 'keycloak-angular'; import { loadRemoteModule } from '@angular-architects/module-federation'; async loadMFE() { // 确保Shell已完成Keycloak登录 await this.keycloakService.isLoggedIn(); // 加载MFE模块 const mfeModule = await loadRemoteModule({ type: 'module', remoteEntry: 'http://your-mfe-domain/remoteEntry.js', exposedModule: './Module' }); // 渲染MFE组件 }
3. MFE的双场景授权处理
MFE需同时支持从Shell集成和独立访问两种场景:
// MFE的app.module.ts初始化逻辑 import { KeycloakService } from 'keycloak-angular'; export function initializeKeycloak(keycloakService: KeycloakService) { return async () => { // 检查是否存在来自Shell的已初始化Keycloak实例 const existingInstance = (window as any).keycloak; if (existingInstance) { // 复用Shell的Keycloak实例,验证令牌受众是否包含MFE const tokenAud = existingInstance.tokenParsed?.aud; if (Array.isArray(tokenAud) && tokenAud.includes('mfe-client-id')) { keycloakService.setKeycloakInstance(existingInstance); } else { // 若令牌不包含MFE受众,启动独立登录流程 await keycloakService.init({ config: { /* MFE的Keycloak配置 */ }, initOptions: { onLoad: 'login-required' } }); } } else { // 独立访问时,初始化MFE自己的Keycloak实例 await keycloakService.init({ config: { url: 'https://your-keycloak-domain/auth', realm: 'your-realm', clientId: 'mfe-client-id' }, initOptions: { onLoad: 'login-required', pkceMethod: 'S256' } }); } }; } @NgModule({ providers: [ { provide: APP_INITIALIZER, useFactory: initializeKeycloak, multi: true, deps: [KeycloakService] } ] }) export class AppModule {}
4. MFE API请求拦截
添加HTTP拦截器,确保MFE的所有API请求携带有效令牌:
// MFE的auth.interceptor.ts import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; import { KeycloakService } from 'keycloak-angular'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private keycloakService: KeycloakService) {} intercept(req: HttpRequest<any>, next: HttpHandler) { const token = this.keycloakService.getKeycloakInstance().token; const authReq = req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); return next.handle(authReq); } }
场景验证
- Shell集成场景:用户登录Shell后,加载MFE时自动复用Shell的令牌(已包含MFE受众),可正常调用MFE API
- 独立访问场景:直接打开MFE域名,自动跳转到Keycloak登录页,获取MFE专属令牌
- 多Shell兼容:Shell1与Shell2按相同配置实现,均可正常集成MFE
内容的提问来源于stack exchange,提问作者BigTman
相关产品推荐
相关产品推荐

