Java从ADAL迁移到MSAL后acquireTokenSilently使用及相关问题咨询
Java ADAL 迁移 MSAL 令牌刷新问题
背景
我们正在将 Java 项目从 ADAL 迁移到 MSAL,采用 OAuth 授权码流获取 refresh token 和 access token。
ADAL 原有流程
- 通过授权码流获取令牌
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);
- 使用 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
相关产品推荐
相关产品推荐

