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

Microsoft Graph Java授权码流多次调用报错咨询token刷新方案

问题根因

  • OAuth2授权码是一次性凭证,仅可用于兑换一次access_token和refresh_token,你当前的代码在每次doGet请求触发时,都会用同一个已过期的授权码重新构建AuthorizationCodeCredential,SDK底层会尝试复用已被兑换的授权码拿 token,因此触发报错。
  • 你已经配置了offline_access权限,Azure Identity SDK 封装的AuthorizationCodeCredential本身自带自动刷新token的能力,不需要手动实现刷新逻辑,问题出在实例的创建逻辑错误。

解决方案

将GraphServiceClient实例按用户维度持久化存储,不要每次请求都重新构建。Servlet场景下可以直接存储在用户的HttpSession中,修改后的代码如下:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    HttpSession session = request.getSession();
    GraphServiceClient graphClient = (GraphServiceClient) session.getAttribute("graphClient");

    // 仅当session中无可用client时,才用授权码新建实例
    if (graphClient == null) {
        String code = request.getParameter("code");
        // 无code也无client,重定向到授权页即可(你原有获取授权码URL的逻辑放这里)
        if (code == null) {
            response.sendRedirect("你的授权码获取URL");
            return;
        }

        String clientId = "***";
        String clientSecret = "***";
        String redirectURL = "http://localhost:8080/O365Web/auth";
        String tenant = "***";

        AuthorizationCodeCredential authCodeCredential = new AuthorizationCodeCredentialBuilder()
                .clientId(clientId)
                .clientSecret(clientSecret)
                .authorizationCode(code)
                .redirectUrl(redirectURL)               
                .build();

        List<String> scopes = new ArrayList<String>();
        scopes.add("https://graph.microsoft.com/User.Read");
        scopes.add("https://graph.microsoft.com/Files.ReadWrite.All");
        scopes.add("https://graph.microsoft.com/offline_access");

        TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(scopes, authCodeCredential);      

        graphClient = GraphServiceClient
                .builder()              
                .authenticationProvider(tokenCredentialAuthProvider)                
                .buildClient();
        
        // 构建完成后存入用户session,后续请求直接复用
        session.setAttribute("graphClient", graphClient);
    }

    // 下面的业务调用逻辑不变,直接复用已有的graphClient实例即可
    try {
        final User me = graphClient.me().buildRequest().get();
        out.println("Name: " + me.displayName + "");
    } catch ( Exception ex ) {
        out.println("Exception in first user get: " + ex.getMessage());    
        ex.printStackTrace();
    }

    try {
        DriveItemCollectionPage children = graphClient.me().drive().root().children().buildRequest().get();

        while ( children != null ) {
            for ( DriveItem item : children.getCurrentPage() ) {
                out.println("C:" + item.name + " : " + item.id);
            }

            DriveItemCollectionRequestBuilder builder = children.getNextPage();
            if ( builder != null ) {
                out.println("Loading page...");
                children = children.getNextPage().buildRequest().get();
            } else {
                children = null;
            }
        }
    } catch ( Exception ex ) {
        out.println("Exception in children get: " + ex.getMessage());   
        ex.printStackTrace();
    }

    try {
        final User me2 = graphClient.me().buildRequest().get();
        out.println(me2.displayName + "<br/>");
    } catch ( Exception ex ) {
        out.println("Exception in second user get: " + ex.getMessage());    
        ex.printStackTrace();
    }
}

注意事项

  • 如果是多实例部署的分布式场景,HttpSession无法跨实例共享,可以将首次兑换得到的refresh token持久化存储到数据库,后续用户访问时直接用RefreshTokenCredential构建GraphServiceClient即可,不需要重复走授权码流程。
  • 敏感信息如clientId、clientSecret不要硬编码在业务代码中,建议放到服务配置文件中做加密存储,避免泄露。
  • AuthorizationCodeCredential会自动管理access token的过期刷新逻辑,只要有有效的refresh token就会自动兑换新的access token,不需要手动调用刷新接口。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 16:54:04