如果我错过了这篇文章中的某些内容,真诚的道歉,因为我在读了好几个小时之后我的智慧结束了。
我正在尝试编写一个后端服务(Windows),它将通过Azure AD连接到MS Graph API。我正在使用C#来破坏概念验证,但是在MS文档,博客等方面遇到了很多问题,这些问题相当令人费解并且质量很差。他们似乎都认为他们的API的消费者是前端桌面或基于浏览器的应用程序。
无论如何,我设法使用服务帐户的UserPasswordCredential连接到Graph,我在AuthenticationContext响应中收到一个令牌,但没有刷新令牌信息。 MSDN建议方法可以返回刷新信息,但是嘿,它可能不会。
此外,阅读(或尝试阅读)ADAL 3上的博客文章(自2015年起):
让我对刷新现在如何运作感到困惑。这篇文章似乎暗示着令人神奇的是令牌在缓存中为你刷新,但在用我的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;
}
}
}
如果有人可以帮助我,而不仅仅是说“以不同的方式做”,我将非常感激,因为似乎很少有关于这个在线讨论(Freenode,IRC)和博客是旧的,文档在MSDN简洁和库的特定先前版本等。例如
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext.AcquireTokenAsync
现在已删除了AuthenticationContext类的刷新方法。
感谢您的任何建议。
彼得
更新
首先感谢@Fei Xue回答我的问题,但是我无法理解他们开始说的话。
与Fei Xue聊天后,似乎如果你保持身份验证上下文,你可以使用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);
}
}
}
我还获得2篇MS文章参考:
Azure AD V2.0 endpoint - client credentials flow
我希望这可以帮助处于同样情况的其他人,就像以前一样,我把头发拉出来了!
答案 0 :(得分:5)
GraphServiceClient
类用于操作无法获取access_token或refresh_token的Microsoft Graph。
正如博客提到的azure-activedirectory-library-for-dotnet库的最新版本没有将refresh_token暴露给开发人员。您可以从AuthenticationResult.cs课程查看。当我们调用方法AcquireTokenSilentAsync
时,如果令牌过期,此库将帮助刷新access_token。
因此,在您的方案中,我们应该使用此方法来获取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;
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;