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
相关产品推荐
相关产品推荐

