在AuthenticationContext中使用UserPasswordCredential进行身份验证时,使用GraphServiceClient获取刷新令牌

pet*_*ski 5 c# authentication azure oauth-2.0 microsoft-graph

真诚的道歉,如果我错过了这篇文章的内容,因为我在读完了几个小时之后我的智慧结束了.

我正在尝试编写一个后端服务(Windows),它将通过Azure AD连接到MS Graph API.我正在使用C#来破坏概念验证,但是在MS文档,博客等方面遇到了很多问题,这些问题相当令人费解并且质量很差.他们似乎都认为他们的API的消费者是前端桌面或基于浏览器的应用程序.

无论如何,我设法使用服务帐户的UserPasswordCredential连接到Graph,我在AuthenticationContext响应中收到一个令牌,但没有刷新令牌信息.MSDN建议该方法可能会返回刷新信息,但嘿嘿,它可能不会.

此外,阅读(或尝试阅读)ADAL 3上的博客文章(从2015年开始):

http://www.cloudidentity.com/blog/2015/08/13/adal-3-didnt-return-refresh-tokens-for-5-months-and-nobody-noticed/

让我对刷新现在的工作方式感到困惑.这篇文章似乎暗示着令人神奇的是令牌在缓存中为你刷新,但在用我的POC测试之后情况并非如此.

我也从MS中看到了这篇文章,似乎在标题中指出:

https://msdn.microsoft.com/en-us/office/office365/howto/building-service-apps-in-office-365

然而,需要你注册应用程序,跳过弹出式箍允许访问等等.

由于我的样本正在运行,我已经尝试安排重新认证,希望在获得初始令牌(持续60分钟)后50分钟获得新令牌但是我得到相同的令牌.这意味着在60分钟1秒内,通过客户端的任何调用都会抛出异常(ServiceException,我必须检查里面的文本以查看令牌到期相关信息).对我来说,无法重新验证和刷新令牌以继续使用客户端进行更"无缝"的使用是没有意义的.

这是我的代码的精简版示例:

namespace O365GraphTest
{
    using Microsoft.Graph;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Nito.AsyncEx;
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using System.Linq;
    using System.Threading;
    using System.Security;

    public class Program
    {
        // Just use a single HttpClient under the hood so we don't hit any socket limits.
        private static readonly HttpProvider HttpProvider = new HttpProvider(new HttpClientHandler(), false);

        public static void Main(string[] args)
        {
            try
            {
                AsyncContext.Run(() => MainAsync(args));
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private static async Task MainAsync(string[] args)
        {
            var tenant = "mytenant.onmicrosoft.com";
            var username = $"test.user@{tenant}";
            var password = "fooooooo";
            var token = await GetAccessToken(username, password, tenant);
            var client = GetClient(token)

            // Example of graph call            
            var skusResult = await client.SubscribedSkus.Request().GetAsync();
        }

        private static async Task<string> GetAccessToken(string username, string password, string tenant = null)
        {
            var authString = tenant == null ?
                $"https://login.microsoftonline.com/common/oauth2/token" :
                $"https://login.microsoftonline.com/{tenant}/oauth2/token";

            var authContext = new AuthenticationContext(authString);
            var creds = new UserPasswordCredential(username, password);
            // Generic client ID
            var clientId = "1950a258-227b-4e31-a9cf-717495945fc2";
            var resource = "https://graph.microsoft.com";

            // NOTE: There's no refresh information here, and re-authing for a token pre-expiry doesn't give a new token.
            var authenticationResult = await authContext.AcquireTokenAsync(resource, clientId, creds);

            return authenticationResult.AccessToken;
        }

        private static GraphServiceClient GetClient(string accessToken, IHttpProvider provider = null)
        {
            var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
            {
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);

                return Task.FromResult(0);
            });

            var graphClient = new GraphServiceClient(delegateAuthProvider, provider ?? HttpProvider);

            return graphClient;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果有人可以帮助我,不用只是说"以不同的方式做",我会非常感激,因为似乎很少有关于这个在线讨论(Freenode,IRC)和博客都很旧,MSDN上的文档简洁以及库的特定先前版本等

Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.AcquireTokenAsync

现在已删除AuthenticationContext类的refresh方法.

谢谢你的建议.

彼得

UPDATE

首先感谢@Fei Xue回答了我的问题,但是我无法理解他们开始说的话.

与Fei Xue聊天之后,似乎如果你保持auth上下文,你可以使用AcquireTokenSilentAsync来获得新的令牌,你不需要处理任何刷新/ reauth等的安排.只需在支票中烘烤委托处理程序(关于您正在调用的方法)是获取客户端的一部分.

这是我测试过的示例代码的更新版本,似乎可以正常工作.

namespace O365GraphTest
{
    using Microsoft.Graph;
    using Microsoft.IdentityModel.Clients.ActiveDirectory;
    using Nito.AsyncEx;
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;

    public class Program
    {
        private const string Resource = "https://graph.microsoft.com";

        // Well known ClientID
        private const string ClientId = "1950a258-227b-4e31-a9cf-717495945fc2";

        private static readonly string Tenant = "mytenant.onmicrosoft.com";

        private static readonly HttpProvider HttpProvider = new HttpProvider(new HttpClientHandler(), false);

        private static readonly AuthenticationContext AuthContext = GetAuthenticationContext(Tenant);

        public static void Main(string[] args)
        {
            try
            {
                AsyncContext.Run(() => MainAsync(args));
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private static async Task MainAsync(string[] args)
        {
            var userName = $"test.user@{Tenant}";
            var password = "fooooooo";
            var cred = new UserPasswordCredential(userName, password);

            // Get the client and make some graph calls with token expiring delays in between.        
            var client = GetGraphClient(cred);
            var skusResult = await client.SubscribedSkus.Request().GetAsync();
            await Task.Delay(TimeSpan.FromMinutes(65));
            var usersResult = await client.Users.Request().GetAsync();
        }

        private static AuthenticationContext GetAuthenticationContext(string tenant = null)
        {
            var authString = tenant == null ?
                $"https://login.microsoftonline.com/common/oauth2/token" :
                $"https://login.microsoftonline.com/{tenant}/oauth2/token";

            return new AuthenticationContext(authString);
        }

        private static GraphServiceClient GetGraphClient(UserPasswordCredential credential)
        {
            var delegateAuthProvider = new DelegateAuthenticationProvider(async (requestMessage) =>
            {
                var result = AuthContext.TokenCache?.Count > 0 ?
                    await AuthContext.AcquireTokenSilentAsync(Resource, ClientId) :
                    await AuthContext.AcquireTokenAsync(Resource, ClientId, credential);
                requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
            });

            return new GraphServiceClient(delegateAuthProvider, HttpProvider);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我也被给了2篇MS文章参考:

验证方案

Azure AD V2.0端点 - 客户端凭据流

我希望这能帮助处于同样情况的其他人,就像之前一样,我把头发拉出来!

Fei*_*SFT 8

GraphServiceClient类用于操作的Microsoft Graph这是不能够获得或的access_token refresh_token.

正如博客中提到的最新版本的azure-activedirectory-library-for-dotnet库不会将refresh_token暴露给开发人员.您可以从AuthenticationResult.cs类中检查它.如果在调用方法时令牌已过期,此库将帮助刷新access_token AcquireTokenSilentAsync.

因此,在您的方案中,我们应该使用此方法来获取访问令牌GraphServiceClient.然后它将始终提供可用的访问令牌GraphServiceClient.以下是供您参考的代码:

string authority = "https://login.microsoftonline.com/{tenant}";
string resource = "https://graph.microsoft.com";
string clientId = "";
string userName = "";
string password = "";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resource, clientId, userPasswordCredential).Result;
var graphserviceClient = new GraphServiceClient(
    new DelegateAuthenticationProvider(
        (requestMessage) =>
        {
            var access_token = authContext.AcquireTokenSilentAsync(resrouce, clientId).Result.AccessToken;
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
            return Task.FromResult(0);
        }));

var a = graphserviceClient.Me.Request().GetAsync().Result;
Run Code Online (Sandbox Code Playgroud)

更新

string authority = "https://login.microsoftonline.com/adfei.onmicrosoft.com";
string resrouce = "https://graph.microsoft.com";
string clientId = "";
string userName = "";
string password = "";
UserPasswordCredential userPasswordCredential = new UserPasswordCredential(userName, password);
AuthenticationContext authContext = new AuthenticationContext(authority);
var result = authContext.AcquireTokenAsync(resrouce, clientId, userPasswordCredential).Result;
var graphserviceClient = new GraphServiceClient(
    new DelegateAuthenticationProvider(
        (requestMessage) =>
        {
            var access_token = authContext.AcquireTokenSilentAsync(resrouce, clientId).Result.AccessToken;
            requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
            return Task.FromResult(0);
        }));

var a = graphserviceClient.Me.Request().GetAsync().Result;

//simulate the access_token expired to change the access_token
graphserviceClient.AuthenticationProvider=
     new DelegateAuthenticationProvider(
         (requestMessage) =>
         {
             var access_token = "abc";
             requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", access_token);
             return Task.FromResult(0);
         });
var b = graphserviceClient.Me.Request().GetAsync().Result;
Run Code Online (Sandbox Code Playgroud)

结果: 在此输入图像描述