如何生成可同时访问MS Graph API与自定义API的通用访问令牌?
解决方案:Azure AD多资源令牌获取与角色授权(Angular + MSAL v3)
核心结论
不存在同时面向Microsoft Graph API和自定义Python API的通用访问令牌。访问令牌的aud(受众)字段是单一资源标识符,这是OAuth2/OpenID Connect的标准要求,Azure AD严格执行此规则。你需要分别获取针对两个资源的独立令牌,但可以通过MSAL的内存缓存机制安全管理,避免存储到本地/会话存储。
1. MSAL v3 Angular配置(内存缓存模式)
在Angular的app.module.ts中配置MSAL,强制使用内存缓存,避免令牌持久化到浏览器存储:
import { NgModule } from '@angular/core'; import { MsalModule, MsalService, MSAL_INSTANCE } from '@azure/msal-angular'; import { PublicClientApplication, BrowserCacheLocation } from '@azure/msal-browser'; export function MSALInstanceFactory(): PublicClientApplication { return new PublicClientApplication({ auth: { clientId: '<你的SPA应用Client ID>', authority: 'https://login.microsoftonline.com/<你的租户ID>', redirectUri: '<你的SPA重定向URI>', }, cache: { cacheLocation: BrowserCacheLocation.Memory, // 仅存内存,页面刷新后清空 storeAuthStateInCookie: false, // 禁用Cookie存储 }, }); } @NgModule({ imports: [ MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: '<你的SPA应用Client ID>', authority: 'https://login.microsoftonline.com/<你的租户ID>', redirectUri: '<你的SPA重定向URI>', }, cache: { cacheLocation: BrowserCacheLocation.Memory, storeAuthStateInCookie: false, }, }), { protectedResourceMap: new Map([ // 自定义Python API的资源映射 ['https://your-python-api-domain/*', ['api://<你的Web API应用Client ID>/Files.Read']], // Microsoft Graph API的资源映射 ['https://graph.microsoft.com/v1.0/me/appRoleAssignments', ['Directory.Read.All', 'User.Read']], ]), } ), ], providers: [ { provide: MSAL_INSTANCE, useFactory: MSALInstanceFactory, }, MsalService, ], }) export class AppModule {}
2. 获取双资源令牌
在Angular服务(比如AuthService)中封装令牌获取逻辑,MSAL会自动管理内存缓存,重复请求会直接返回缓存的令牌:
import { Injectable } from '@angular/core'; import { MsalService } from '@azure/msal-angular'; import { AuthenticationResult } from '@azure/msal-browser'; @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private msalService: MsalService) {} // 获取自定义Python API的访问令牌 async getApiToken(): Promise<string> { const result: AuthenticationResult = await this.msalService.acquireTokenSilent({ scopes: ['api://<你的Web API应用Client ID>/Files.Read'], }); return result.accessToken; } // 获取Microsoft Graph API的访问令牌 async getGraphToken(): Promise<string> { const result: AuthenticationResult = await this.msalService.acquireTokenSilent({ scopes: ['Directory.Read.All', 'User.Read'], }); return result.accessToken; } // 调用Graph API获取用户角色 async getUserRoles(): Promise<string[]> { const token = await this.getGraphToken(); const response = await fetch('https://graph.microsoft.com/v1.0/me/appRoleAssignments', { headers: { Authorization: `Bearer ${token}` }, }); const data = await response.json(); // 提取角色名称(假设你的角色定义在appRole的displayName字段) return data.value.map((assignment: any) => assignment.appRole.displayName); } // 检查用户是否拥有指定角色 async hasRole(roleName: string): Promise<boolean> { const roles = await this.getUserRoles(); return roles.includes(roleName); } }
3. 基于角色控制视图显示
在Angular组件中直接调用AuthService的方法控制视图:
<!-- 仅当用户拥有Admin角色时显示此组件 --> <div *ngIf="authService.hasRole('Admin') | async"> <h2>管理员控制面板</h2> <!-- 管理员专属内容 --> </div> <!-- 普通用户视图 --> <div *ngIf="!(authService.hasRole('Admin') | async)"> <h2>用户主页</h2> <!-- 普通用户内容 --> </div>
4. 关键注意事项
- 权限配置:需要在Azure AD的SPA应用中添加Microsoft Graph的
Directory.Read.All(委派权限),并由租户管理员完成权限同意。 - 令牌刷新:MSAL会自动处理令牌过期刷新,内存缓存中的令牌会被自动更新,无需手动干预。
- 角色替代方案:如果你的自定义API令牌中已经包含
roles声明(Azure AD会自动将分配的角色添加到令牌的roles字段),可以直接从API令牌中提取角色,无需调用Graph API,减少网络请求。
内容的提问来源于stack exchange,提问作者Nitesh Ratnaparkhe
相关产品推荐
相关产品推荐

