Spring Boot后端如何利用Azure AD令牌调用Microsoft Graph?
能否直接用Web App传来的Azure AD令牌调用Microsoft Graph?解决方案详解
一、不能直接使用的原因
- 令牌受众不匹配:Web App传给你API的令牌,其
aud(受众)字段是你的API应用ID URI(比如api://your-api-client-id),而Microsoft Graph要求令牌的aud必须是https://graph.microsoft.com,否则会直接拒绝请求。 - 权限范围不匹配:该令牌的权限是针对你的API配置的,不会包含调用Microsoft Graph所需的权限(比如
User.Read),即使你的Azure AD应用已配置Graph权限,原令牌也不会自动携带这些权限范围。
二、正确解决方案:使用On-Behalf-Of(OBO)流程获取Graph令牌
因为你只能拿到用户的访问令牌,所以需要通过OBO流程代表用户向Azure AD请求专门用于调用Microsoft Graph的访问令牌。以下是Spring Boot+Java的具体实现步骤:
1. 前置Azure AD配置
- 确保你的后端API应用已在Azure AD注册,并添加Microsoft Graph的Delegated权限(比如
User.Read,用于读取用户基本信息),完成管理员同意。 - 确保Web App注册的应用已被授予调用你API的Delegated权限,API应用默认支持OBO流程,无需额外配置。
2. 项目依赖
在pom.xml中添加Azure AD和Microsoft Graph SDK依赖:
<dependency> <groupId>com.azure.spring</groupId> <artifactId>azure-spring-boot-starter-active-directory</artifactId> <version>4.14.0</version> </dependency> <dependency> <groupId>com.microsoft.graph</groupId> <artifactId>microsoft-graph</artifactId> <version>6.0.0</version> </dependency>
3. 配置文件(application.yml)
填入你的Azure AD租户ID、API应用的客户端ID和密钥:
spring: cloud: azure: active-directory: tenant-id: ${AZURE_TENANT_ID} client-id: ${AZURE_CLIENT_ID} client-secret: ${AZURE_CLIENT_SECRET} app-id-uri: ${YOUR_API_APP_ID_URI} # 你的API的受众,例如api://xxxx-xxxx-xxxx-xxxx
4. 代码实现OBO流程并调用Graph
import com.azure.core.credential.AccessToken; import com.azure.identity.OnBehalfOfCredential; import com.azure.identity.OnBehalfOfCredentialBuilder; import com.microsoft.graph.models.User; import com.microsoft.graph.requests.GraphServiceClient; import okhttp3.OkHttpClient; import okhttp3.Request; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.CompletableFuture; @RestController public class UserExtraInfoController { private final String tenantId = System.getenv("AZURE_TENANT_ID"); private final String clientId = System.getenv("AZURE_CLIENT_ID"); private final String clientSecret = System.getenv("AZURE_CLIENT_SECRET"); // 定义需要的Graph权限范围 private final String[] graphScopes = {"https://graph.microsoft.com/User.Read"}; @GetMapping("/api/user/extra-info") public CompletableFuture<User> fetchExtraUserInfo(@RequestHeader("Authorization") String authHeader) { // 提取前端传入的访问令牌(去除Bearer前缀) String userAccessToken = authHeader.replaceFirst("Bearer ", ""); // 初始化OBO凭证,用于交换Graph令牌 OnBehalfOfCredential oboCredential = new OnBehalfOfCredentialBuilder() .tenantId(tenantId) .clientId(clientId) .clientSecret(clientSecret) .userAssertion(userAccessToken) .build(); // 请求Graph访问令牌 return oboCredential.getToken(new com.azure.core.credential.TokenRequestContext().addScopes(graphScopes)) .thenApply(this::callMicrosoftGraph); } private User callMicrosoftGraph(AccessToken graphToken) { // 配置Graph客户端,注入令牌 OkHttpClient httpClient = new OkHttpClient.Builder() .addInterceptor(chain -> { Request authenticatedRequest = chain.request().newBuilder() .header("Authorization", "Bearer " + graphToken.getToken()) .build(); return chain.proceed(authenticatedRequest); }) .build(); GraphServiceClient<okhttp3.Request> graphClient = GraphServiceClient.builder() .httpClient(httpClient) .buildClient(); // 调用Graph API获取用户信息(这里以获取指定字段为例) return graphClient.me() .buildRequest() .select("id,displayName,mail,userPrincipalName,department") .get(); } }
5. 注意事项
- 确保前端传入的是**访问令牌(Access Token)**而非ID Token,ID Token无法用于OBO流程交换。
- 可以根据令牌的
expiresOn时间缓存Graph令牌,避免重复发起OBO请求,提升性能。 - 处理令牌过期、权限不足等异常情况,捕获Azure Identity SDK抛出的认证异常,返回合适的HTTP状态码。
内容的提问来源于stack exchange,提问作者Quim PerVal
相关产品推荐
相关产品推荐

