Azure AD多资源additionalLoginParams配置与跨后端授权方案咨询
针对你的Azure AD多后端授权场景,我给你详细拆解两个方案的实现步骤,都是基于Azure AD官方推荐的流程:
这个方案的核心是利用Azure AD v2.0端点的**范围(Scope)**机制代替旧的resource参数,因为v1.0不支持同时指定多个资源,而v2.0允许在一个请求中包含多个API的访问范围。
步骤1:切换到v2.0端点并配置后端API范围
首先,把你的应用切换到Azure AD v2.0端点(如果还没切换的话)。然后给两个后端应用分别暴露API访问范围:- 进入Backend-1的Azure AD注册页 → 「暴露API」标签 → 点击「添加范围」,创建一个用户级的范围(比如
access_as_user,设置同意方式为“管理员和用户”) - 重复上面的操作,给Backend-2也创建相同或自定义的范围(比如
access_as_user)
- 进入Backend-1的Azure AD注册页 → 「暴露API」标签 → 点击「添加范围」,创建一个用户级的范围(比如
步骤2:给前端应用添加双后端的权限
- 打开前端应用的Azure AD注册页 → 「API权限」标签 → 点击「添加权限」
- 选择「我的API」,分别选中Backend-1和Backend-2,勾选你刚才创建的
access_as_user范围,然后点击「添加权限」 - 记得点击「授予管理员同意」(如果是企业租户环境,普通用户可能没有同意权限)
步骤3:修改前端应用的
additionalLoginParams配置
把原来的多个resource参数替换成单个scope参数,把两个后端的API范围和OpenID基础范围(用于ID Token)放在一起:"additionalLoginParams": [ "response_type=code id_token", "scope=openid profile api://<Backend-1-app-id>/access_as_user api://<Backend-2-app-id>/access_as_user" ]这里的
openid profile是必须的,用于获取ID Token做身份认证,后面跟着两个后端的完整scope路径(格式是api://<应用ID>/<范围名称>)步骤4:后端验证逻辑调整
两个后端在验证前端的Access Token时,不要只检查aud(受众)字段,而是要检查scp(范围)声明是否包含自己的API范围。比如Backend-2要确认token里的scp包含api://<Backend-2-app-id>/access_as_user,同时azp(客户端ID)是前端应用的ID,确保是合法的前端请求。
这个方案更安全,因为Backend-2的访问Token只有Backend-1能获取,前端无法直接拿到,适合需要隔离后端权限的场景。核心是用OAuth2的On-Behalf-Of流程,让Backend-1用前端的Token作为断言,向Azure AD申请Backend-2的访问Token。
步骤1:配置Backend-1的Azure AD权限
- 进入Backend-1的Azure AD注册页 → 「API权限」标签 → 点击「添加权限」
- 选择「我的API」→ 选中Backend-2,勾选之前创建的
access_as_user委托权限,点击「添加权限」 - 同样点击「授予管理员同意」,确保Backend-1有申请Backend-2 Token的权限
步骤2:在NestJS的Backend-1中实现OBO流程
首先安装Azure AD的MSAL Node库:npm install @azure/msal-node然后在Backend-1中初始化MSAL客户端,并实现获取OBO Token的方法:
import { ConfidentialClientApplication } from '@azure/msal-node'; // 初始化MSAL客户端(可以放在配置文件或模块中) const msalConfig = { auth: { clientId: '<Backend-1的应用ID>', authority: 'https://login.microsoftonline.com/<你的租户ID>', clientSecret: '<Backend-1的客户端密钥>', // 生产环境推荐用证书代替密钥 }, }; const cca = new ConfidentialClientApplication(msalConfig); // 获取OBO Token的方法 async getBackend2Token(frontendAccessToken: string): Promise<string> { const oboRequest = { oboAssertion: frontendAccessToken, scopes: ['api://<Backend-2的应用ID>/access_as_user'], skipCache: false, // 开启缓存减少Azure AD请求次数 }; try { const response = await cca.acquireTokenOnBehalfOf(oboRequest); return response?.accessToken || ''; } catch (error) { console.error('获取Backend-2 Token失败:', error); throw new Error('无法获取后端服务权限'); } }最后,在调用Backend-2的接口中使用这个Token:
async invokeBackend2Task(frontendToken: string) { const backend2Token = await this.getBackend2Token(frontendToken); const response = await fetch('https://你的Backend-2地址/api/xxx', { method: 'POST', headers: { 'Authorization': `Bearer ${backend2Token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ /* 请求体 */ }), }); if (!response.ok) { throw new Error('调用Backend-2失败'); } return response.json(); }步骤3:Backend-2的Token验证
Backend-2在验证Token时,需要确认:aud(受众)是自己的应用IDiss(颁发者)是Azure AD的租户端点scp包含对应的访问范围azp(客户端ID)是Backend-1的应用ID,确保是Backend-1发起的请求
内容的提问来源于stack exchange,提问作者Sirjun Sagarino

