React SPA+Spring Boot对接Microsoft Graph的OAuth2.0流选型及疑问
核心问题1:OAuth2.0流的选择
直接给出结论:必须使用on-behalf-of(OBO)流,另外两个选项都不适合你的场景,原因如下:
常规授权码流(非PKCE):完全不适合SPA。SPA运行在浏览器环境,无法安全存储客户端密钥——任何存到前端的密钥都能被开发者工具提取,一旦泄露,攻击者可以冒充你的应用发起请求,严重违反OAuth2.0的安全规范,这也是Auth0和微软都不推荐SPA使用该流的原因。
带PKCE的授权码流:这是SPA本身身份认证的标准流,但仅靠它无法满足你的需求。前端通过该流获取的访问令牌,受众(
aud字段)是你的Spring Boot后端API,不是Microsoft Graph API,后端没法直接用这个令牌调用Graph。而且如果让前端直接调Graph,会把用户令牌暴露在前端,增加泄露风险,同时也不符合你“后端代表用户调用Graph”的需求。on-behalf-of流:这是专门为“前端已认证用户,后端需要代表用户调用第三方API”的场景设计的,是PKCE流的扩展,完整流程:
- 前端通过PKCE授权码流,获取针对你的后端API的访问令牌
- 前端在向后端发起请求时,将该令牌放在
Authorization: Bearer <token>头中传递 - 后端使用这个令牌,加上自己的客户端凭据(客户端ID+密钥,后端可安全存储),向Azure AD申请针对Microsoft Graph API的访问令牌(代表当前用户)
- 后端用这个新令牌调用Microsoft Graph API
你之前的疑问“为何不能后端用客户端密钥直接拿令牌”,本质是混淆了客户端凭据流和代表用户的流。后端用客户端密钥拿的令牌是代表应用本身,没有用户上下文,只能访问应用权限对应的资源(比如租户级别的数据),无法访问用户的个人数据(比如用户的邮箱、日历),完全不符合你“代表用户调用Graph”的需求。
核心问题2:on-behalf-of流的令牌使用方式
后端不能直接用前端传来的令牌调用Microsoft Graph API,原因有两点:
- 前端令牌的受众是你的后端API,不是Graph API,Azure AD会直接拒绝这个请求
- 前端令牌的权限范围是针对后端API的,没有Graph API的访问权限
正确的做法是:后端收到前端的令牌后,发起OBO令牌请求,向Azure AD申请一个新的、受众为Microsoft Graph API的访问令牌,用这个新令牌去调用Graph接口。这个新令牌会继承前端令牌的用户身份和权限范围(当然你需要在应用注册时配置好对应的Delegated权限)。
关于@microsoft/mgt-react库的使用
这个库已经封装了前端的PKCE流逻辑,你不需要自己去获取授权码。你只需要配置好Provider,让前端获取到针对你的后端API的访问令牌,然后在向后端发请求时,把这个令牌放到请求头里即可。示例代码如下:
import { useMsal } from '@azure/msal-react'; import axios from 'axios'; const { instance, accounts } = useMsal(); const tokenRequest = { scopes: ["api://<你的后端API客户端ID>/access_as_user"], // 后端API的权限范围 account: accounts[0] }; const getToken = async () => { const response = await instance.acquireTokenSilent(tokenRequest); return response.accessToken; }; // 向后端发请求时携带令牌 const callBackend = async () => { const token = await getToken(); const response = await axios.get('/api/graph-data', { headers: { Authorization: `Bearer ${token}` } }); };
后端则需要配置OBO流程的逻辑,用收到的令牌去申请Graph的令牌,然后调用Graph API。
内容的提问来源于stack exchange,提问作者user2779450

