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

Java从ADAL迁移到MSAL后acquireTokenSilently使用及相关问题咨询

Java ADAL 迁移 MSAL 令牌刷新问题

背景

我们正在将 Java 项目从 ADAL 迁移到 MSAL,采用 OAuth 授权码流获取 refresh token 和 access token。

ADAL 原有流程

  1. 通过授权码流获取令牌
ClientCredential credential =
        new ClientCredential(
                PRODUCTION_OAUTH_SECRETS.getClientId(), PRODUCTION_OAUTH_SECRETS.getClientSecret());

AuthenticationContext context =
        new AuthenticationContext(
                "https://login.microsoftonline.com/" + domainName+ "/", true, service);
Future<AuthenticationResult> future =
        context.acquireTokenByAuthorizationCode(
                code, new URI(returnPath), credential, null);
AuthenticationResult result = future.get();

OAuth2Token token =
        new OAuth2Token(
                result.getAccessToken(), result.getRefreshToken(), result.getAccessTokenType(), null);
  1. 使用 refresh token 刷新 access token
AuthenticationContext context =
        new AuthenticationContext(
                "https://login.microsoftonline.com/" + domainName+ "/", true, service);
Future<AuthenticationResult> future =
        context.acquireTokenByRefreshToken(credentials.refreshToken, getClientCredential(), null, null);
AuthenticationResult result = future.get();

credentials.refreshToken = result.getRefreshToken();
credentials.accessToken = result.getAccessToken();
credentials.expiresAt = result.getExpiresOnDate().toInstant();

MSAL 现有实现

已完成迁移,成功通过授权码流获取初始令牌:

ConfidentialClientApplication app =
        ConfidentialClientApplication.builder(
                        clientId, ClientCredentialFactory.createFromSecret(clientSecret))
                .authority("https://login.microsoftonline.com/" + domainName+ "/")
                .build();

AuthorizationCodeParameters parameters =
        AuthorizationCodeParameters.builder(code, returnPath)
                .scopes(Collections.singleton("offline_access"))
                .build();

CompletableFuture<IAuthenticationResult> future = app.acquireToken(parameters);
IAuthenticationResult result = future.get();

但尝试以下代码刷新令牌时,因缓存无令牌失败:

ConfidentialClientApplication cca = ConfidentialClientApplication.builder(oAuthClientSecret.getClientId(), ClientCredentialFactory.createFromSecret(oAuthClientSecret.getClientSecret()))
        .authority("https://login.microsoftonline.com/" + domainName+ "/")
        .build();

// define the scopes to request
String scopes =  "offline_access";
// acquire an access token silently
IAuthenticationResult authResult = cca.acquireTokenSilently(
        SilentParameters.builder(Collections.singleton(scopes)).build()).join();

credentials.accessToken = authResult.accessToken();

问题解答

1. 如何正确使用 acquireTokenSilently 方法

acquireTokenSilently 依赖 MSAL 的令牌缓存来获取或自动刷新令牌,你失败的核心原因是每次刷新都新建了全新的 ConfidentialClientApplication 实例,新实例的缓存为空,自然找不到令牌。

正确操作方式:

  • 复用同一个 ConfidentialClientApplication 实例(建议做成单例),确保令牌缓存能在多次请求中保留;
  • 如果必须重新创建实例,需手动加载之前保存的令牌缓存数据(比如从数据库/文件读取后注入);
  • 调用时必须指定用户账号信息(缓存按账号区分),可从首次获取令牌的 result.account() 中提取并保存。

示例代码:

// 复用已初始化的 app 实例(或加载缓存后构建)
IAccount savedAccount = ...; // 首次获取令牌时保存的账号信息

SilentParameters silentParams = SilentParameters.builder(Collections.singleton("<你的API权限Scope>"), savedAccount)
        .build();

CompletableFuture<IAuthenticationResult> future = app.acquireTokenSilently(silentParams);
IAuthenticationResult authResult = future.get();

另外,若缓存中无有效令牌,acquireTokenSilently 会抛出异常,建议捕获后回退到重新引导用户授权的流程。

2. 当前配置的 scopes 是否正确

你当前仅配置 offline_access 是不完整的:

  • offline_access 是用于获取 refresh token 的特殊权限,本身无法用来获取访问 API 的 access token;
  • 必须同时添加实际需要访问的 API 权限 Scope(比如对应业务 API 的具体权限)。

正确的 Scope 配置示例:

List<String> scopes = Arrays.asList("offline_access", "https://your-api-scope.example.com/access");

AuthorizationCodeParameters parameters =
        AuthorizationCodeParameters.builder(code, returnPath)
                .scopes(scopes)
                .build();

刷新令牌时,acquireTokenSilently 使用的 Scope 需与首次请求一致(或为其子集),不能仅传 offline_access。

3. MSAL 令牌缓存的具体位置

MSAL Java 默认使用内存缓存,缓存数据存储在 ConfidentialClientApplication 实例内部,实例销毁后缓存会直接丢失。

如果需要持久化缓存(比如服务重启后仍能复用令牌),需实现自定义的 ITokenCacheAccessAspect 接口,将缓存数据序列化后存储到数据库、文件或其他持久化介质中:

  • 在 beforeCacheAccess 方法中从持久化存储读取缓存并加载到内存;
  • 在 afterCacheAccess 方法中将内存中的缓存数据序列化后保存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 10:08:11