You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot+Microsoft Graph API通过Client_Secret读取Outlook邮件遇403错误求助

SpringBoot通过Client Secret读取Outlook邮件遇403禁止访问问题解决

问题详情

已实现获取Access Token,但Refresh Token、ID Token长度为0,调用Microsoft Graph API读取收件箱时返回403:

GET https://graph.microsoft.com/v1.0/users/di.a@help.com/mailFolders/inbox/messages?$top=5

错误提示:

Access is denied. Check credentials and try again.
403 : Forbidden
SdkVersion : graph-java/v4.1.0
[...]
[部分信息已截断,启用调试日志可查看更多详情]

相关代码

public static void initializeGraph() throws MalformedURLException, ExecutionException, InterruptedException {    
    Set<String> SCOPE = new HashSet<>();
    SCOPE.add("https://graph.microsoft.com/.default");
    SCOPE.add("offline_access");

    IClientCredential credential = ClientCredentialFactory.createFromSecret(CLIENT_SECRET);

    ConfidentialClientApplication cca = ConfidentialClientApplication
            .builder(CLIENT_ID, credential)
            .authority(AUTHORITY)
            .build();

    IAuthenticationResult result;
    ITokenCache tokenCache = cca.tokenCache();

    try {
        SilentParameters silentParameters = SilentParameters
                .builder(SCOPE)
                .build();

        // 尝试静默获取Token
        result = cca.acquireTokenSilently(silentParameters).get();
        System.out.println(result);

    } catch (Exception ex) {
        if (ex.getCause() instanceof MsalException) {
            ClientCredentialParameters parameters = ClientCredentialParameters
                    .builder(SCOPE)
                    .build();

            // 获取Token
            IAuthenticationResult accessToken = cca.acquireToken(parameters).get();

            System.out.println("Token retrieved from accessToken: " + accessToken.accessToken());
            System.out.println("Token retrieved from idToken: " + accessToken.idToken());
            System.out.println("Token retrieved from tenantProfile: " + accessToken.tenantProfile());
            System.out.println("Token retrieved from expiresOnDate: " + accessToken.expiresOnDate());

            TokenCredential tokenCredential = new TokenCredential() {
                @Override
                public Mono<com.azure.core.credential.AccessToken> getToken(TokenRequestContext tokenRequestContext) {
                    String accessToken = accessToken.accessToken();
                    Instant instant = accessToken.expiresOnDate().toInstant();
                    com.azure.core.credential.AccessToken azureToken = new
                            com.azure.core.credential.AccessToken(accessToken,
                            instant.atOffset(ZoneId.systemDefault().getRules().getOffset(instant)));
                    return Mono.just(azureToken);
                }
            };

            IAuthenticationProvider authProvider = new TokenCredentialAuthProvider(tokenCredential);

            userClient = GraphServiceClient
                    .builder()
                    .authenticationProvider(authProvider)
                    .buildClient();
        } else {
            throw ex;
        }
    }
}

public static MessageCollectionPage getInbox() throws Exception { 
    initializeGraphForAppOnlyAuth(); 
    System.out.println(userClient); 
    if (userClient == null) {
        throw new Exception("Graph has not been initialized for user auth");
    }      
  
    return userClient.users("di.a@help.com")
            .mailFolders("inbox")
            .messages()
            .buildRequest()
            .top(5)
            .get();
}


public static MessageCollectionPage getInbox() throws Exception { 
initializeGraphForAppOnlyAuth(); 
System.out.println(userClient); 
if (userClient == null) {
throw new Exception("Graph has not been initialized for user auth");
 }      
  
return userClient.users("di.a@help.com")
                .mailFolders("inbox")
                .messages()
                .buildRequest()
                .top(5)
                .get();
}

解决步骤

1. 配置正确的应用权限

客户端凭证流(Client Credentials Flow)需要分配应用权限(Application Permissions),而非委托权限:

  • 登录Azure门户 → 进入你的应用注册 → 【API权限】→ 【添加权限】→ 选择Microsoft Graph → 【应用权限】。
  • 搜索并勾选Mail.Read或Mail.ReadWrite权限,点击【添加权限】。
  • 点击【授予管理员同意】,确保权限生效。

2. 修正作用域配置

客户端凭证流中不需要offline_access作用域,仅保留.default即可:

Set<String> SCOPE = new HashSet<>();
SCOPE.add("https://graph.microsoft.com/.default");

3. 理解Token返回逻辑

  • 客户端凭证流是应用级认证,无用户参与,因此不会返回ID Token(用户身份凭证)和Refresh Token(应用可直接重新获取Token),此为正常现象,无需处理。

4. 修复代码中的问题

  • 方法名不一致:getInbox调用的initializeGraphForAppOnlyAuth()与提供的初始化方法initializeGraph()名称不符,需统一方法名。
  • 变量名冲突:匿名内部类中使用的accessToken与外部变量重名,导致编译错误,重命名外部变量即可:
    // 重命名外部变量为authResult
    IAuthenticationResult authResult = cca.acquireToken(parameters).get();
    
    // 内部类中使用authResult
    TokenCredential tokenCredential = new TokenCredential() {
        @Override
        public Mono<com.azure.core.credential.AccessToken> getToken(TokenRequestContext tokenRequestContext) {
            String token = authResult.accessToken();
            Instant instant = authResult.expiresOnDate().toInstant();
            com.azure.core.credential.AccessToken azureToken = new
                    com.azure.core.credential.AccessToken(token,
                    instant.atOffset(ZoneId.systemDefault().getRules().getOffset(instant)));
            return Mono.just(azureToken);
        }
    };
    

5. 验证Authority格式

确保AUTHORITY使用正确的租户ID:

https://login.microsoftonline.com/{你的租户ID}

不可使用common或consumers,客户端凭证流仅支持指定租户。

内容的提问来源于stack exchange,提问作者Rishi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 00:54:53