Azure:无法使用 RefreshToken 获取新的 AccessToken

Posted

技术标签:

【中文标题】Azure:无法使用 RefreshToken 获取新的 AccessToken【英文标题】:Azure: Unable to use RefreshToken to acquire a new AccessToken 【发布时间】:2017-02-01 22:41:50 【问题描述】:

我正在构建一个需要访问我们客户的Office 365 Management Activities 的应用程序。我已按照this Azure Active Directory 概述中概述的步骤进行操作,并且能够使用 OAuth 代码获取初始访问令牌,以及使用此令牌来设置 O365 订阅。

但是,当我使用随初始令牌提供的refresh_token 获取新的访问令牌时,出现以下错误:

"error_description":"AADSTS65001: 用户或管理员未同意使用 ID 为“8f72f805-dfd2-428d-8b0e-771a98d26c16”的应用程序。为此用户和资源发送交互式授权请求。\r\ nTrace ID:df229c3f-8f28-420b-9ac3-321ab1b2ad09\r\n相关 ID:0e0f2bcb-4b19-458a-8556-2a6d4e51379f\r\n时间戳:2016-10-03 17:33:20Z","error":"invalid_grant "

由于我能够获取和使用初始访问令牌,我很确定用户正在授予我的应用程序一些权限。是否需要特定权限才能使用刷新令牌获取新的访问令牌?

编辑: 具体来说,我使用的是com.microsoft.azure::adal4jjava package、AuthenticationContext 类、acquireTokenByAuthorizationCode 和acquireTokenByRefreshToken 方法:

public class AzureProvisioner 
    private final AuthenticationContext authService = new AuthenticationContext(
            "https://login.windows.net/common/oauth2/token", true, Executors.newSingleThreadExecutor());
    private final ClientCredential clientCredential = new ClientCredential("azureAppId", "azureAppSecret");
    public static final String resource = "https://manage.office.com";
    // Internal implementation of REST interface; Microsoft didn't provide a Java Library
    final Office365ManagementApi managementApi;

    public void acquireToken(final String authCode, final URI redirectUri) 
        final AuthenticationResult authResult = authService.acquireTokenByAuthorizationCode(
                authCode, redirectUri, clientCredential, resource, null).get()
        // internal library code, gets the "tid" field from parsing the JWT token
        final String tenantId = JwtAccessToken.fromToken(authResult.getAccessToken()).getTid();

        // works
        createInitialSubscription(customerId, authResult.getAccessToken(), tenantId);

        // throws an error
        final AuthenticationResult refreshResult = authService.acquireTokenByRefreshToken(
                authResult.getRefreshToken(), clientCredential, null).get();
    

    private void createInitialSubscription(final String accessToken, final String tenantId) 
        final String authHeader = "Authorization: Bearer " + accessToken;
        final String contentType = "Audit.AzureActiveDirectory";
        // internal implementation
        final CreateWebhookRequest requestBody = new CreateWebhookRequest();
        managementApi.createSubscription(authHeader, tenantId, contentType, requestBody);
    

同样的代码,没有任何外部依赖,对我也不起作用:

public class AzureProvisioner 
    private final AuthenticationContext authService = new AuthenticationContext(
            "https://login.windows.net/common/oauth2/token", true, Executors.newSingleThreadExecutor());
    private final ClientCredential clientCredential = new ClientCredential("8f72f805-dfd2-428d-8b0e-771a98d26c16", "secret");
    public final String resource = "https://manage.office.com";
    private URI redirectUri = new URI("https://localhost");

    private static final String oAuthUrl = "https://login.windows.net/common/oauth2/authorize?response_type=code&client_id=8f72f805-dfd2-428d-8b0e-771a98d26c16&resource=https%3A%2F%2Fmanage.office.com&redirect_uri=https%3A%2F%2Flocalhost";

    public AzureProvisioner() throws Exception 
        // do nothing
    

    public static void main(String... args) throws Exception 
        final String authCode = "AQABAAAAAADRNYRQ3dhRSrm...";
        new AzureProvisioner().acquireToken(authCode);
    

    public void acquireToken(final String authCode) throws Exception 
        final AuthenticationResult authResult = authService.acquireTokenByAuthorizationCode(
                authCode, redirectUri, clientCredential, resource, null).get();
        System.out.println(authResult.getAccessToken());

        // throws an error
        final AuthenticationResult refreshResult = authService.acquireTokenByRefreshToken(
                authResult.getRefreshToken(), clientCredential, resource, null).get();
        System.out.println(refreshResult.getAccessToken());
    

使用代理,我跟踪了 https 刷新请求:

Method: POST
Protocol-Version: HTTP/1.1
Protocol: https
Host: login.windows.net
File: /common/oauth2/token
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 876

refresh_token=token
&resource=https%3A%2F%2Fmanage.office.com
&grant_type=refresh_token
&scope=openid
&client_secret=secret
&client_id=8f72f805-dfd2-428d-8b0e-771a98d26c16

【问题讨论】:

您能否说明您是如何获取初始访问令牌和刷新令牌的,以及您是如何尝试使用刷新令牌的? 用代码示例更新了问题。 我可以毫无错误地运行上面的内容(注释掉引用CreateWebhookRequestOffice365ManagementApi 的行)。您能否添加一个仍能重现该问题的代码版本,但除了 ADAL4J 之外不依赖任何东西? 真的吗?我还在本地尝试了一个配对版本,它只获取一个令牌并立即尝试刷新它。 (在上面进行了编辑。)您的 Azure 应用程序是否具有我可能缺少的任何必需权限?我以全局管理员的身份获取 OAuth 代码 - 这可能是个问题吗? @dstrokis UserInfo 中没有 tid 或类似名称。 @AndrewRueckert,如果您愿意,可以查看 ID 令牌 (authResult.getIdToken())。 【参考方案1】:

事实证明,根本问题在于我的应用程序权限。在My Application > Settings > Required Permissions > Office 365 Management APIs 下,我选择了“应用程序权限”,在这里我需要选择“委派权限”。交换这些,我的代码立即开始按预期工作。

【讨论】:

【参考方案2】:

ADAL 自动且透明地使用存储的刷新令牌,您无需执行任何显式操作。由于遗留原因,AcquireTOkenByRefreshToken 位于 ADAL 表面,已从版本 3.x 中删除。更多背景信息http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/

【讨论】:

这似乎是一个用于客户端应用程序的 C# 库。我正在为基于多租户服务器的应用程序编写 Java 代码。 这里的重点是解释刷新令牌过程的使用是自动和透明的,与您使用的语言无关,但在幕后您正在使用 AAD。如果您创建的新令牌由于存在尚未过期的令牌而无法验证。 @MitinDixit:不,这不是真的。 ADAL for .NET 中包含的令牌缓存尚未在 ADAL for Java 中实现。 @AndrewRueckert 这个问题看起来很像***.com/questions/34775287/… 我看过了,但不幸的是它略有不同。该用户无法获得初始令牌,因为他们没有以管理员用户身份登录。我以管理员用户身份登录,并且能够获得初始令牌;但是,我无法刷新它。

以上是关于Azure:无法使用 RefreshToken 获取新的 AccessToken的主要内容,如果未能解决你的问题,请参考以下文章

Azure 无法将事务编组为弹性事务的传播令牌(适用于 MSDTC)

无法从 Azure B2C 获取刷新令牌

Cookie、Session、Token、RefreshToken

Firebase:我啥时候应该使用 refreshToken?

Firebase 使用 idToken 或 refreshToken 登录

在 Azure 中创建一个简单的 ***