通过MVC Web App中的ViewModel调用Graph API

我正在尝试使用Graph API来创建我自己的Web应用程序导航栏的“用户配置文件”部分。 为此,我对我的UserProfile控制器的GetUser操作进行了AJAX调用:

$.ajax({ type: "GET", url: "@Url.Action("GetUser", "UserProfile", null)", dataType: "json", success: function (data, status, xhr) { console.log("in AJAX"); $(".img-circle, .user-image").attr("src", data.Picture); $("#user-menu-expanded").text(data.User.DisplayName + " - " + data.User.JobTitle); $("#user-menu-spinner").remove(); console.log(data); }, error: function (ex) { console.log(ex); } }); 

控制器将我的UserProfileViewModel作为Json返回,我用它来替换上面的元素,如我的AJAX成功函数所示。

UserProfile控制器:

  public JsonResult GetUser() { var model = new UserProfileViewModel(); return Json(model, JsonRequestBehavior.AllowGet); } 

我的UserProfileViewModel看起来像这样:

  public UserProfileViewModel() { var graphClient = GetAuthGraphClient(); GetPicture(graphClient); GetUserProfile(graphClient); } public GraphServiceClient GetAuthGraphClient() { string graphResourceID = "https://graph.microsoft.com/"; return new GraphServiceClient( new DelegateAuthenticationProvider((requestMessage) => { string accessToken = GetTokenForApplication(graphResourceID); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken); return Task.FromResult(0); } )); } public string GetTokenForApplication(string graphResourceID) { string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value; string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; string authority = "https://login.microsoftonline.com/" + tenantID; try { ClientCredential clientcred = new ClientCredential(clientId, appKey); // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database AuthenticationContext authenticationContext = new AuthenticationContext(authority); var token = authenticationContext.AcquireTokenAsync(graphResourceID, clientcred).Result.AccessToken; return token; } catch (Exception e) { // Capture error for handling outside of catch block ErrorMessage = e.Message; return null; } } public void GetPicture(GraphServiceClient graphClient) { Stream photo = Task.Run(async () => { return await graphClient.Me.Photo.Content.Request().GetAsync(); }).Result; using (var memoryStream = new MemoryStream()) { photo.CopyTo(memoryStream); var base64pic = Convert.ToBase64String(memoryStream.ToArray()); this.Picture = "data:image;base64," + base64pic; HttpContext.Current.Cache.Add("Pic", this.Picture, null, DateTime.Now.AddHours(5), Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null); } } public void GetUserProfile(GraphServiceClient graphClient) { this.User = Task.Run(async () => { return await graphClient.Me.Request().GetAsync(); }).Result; } 

我成功获取了访问令牌,但是我的AJAX调用没有返回任何数据。
从IIS日志 控制台日志 访问令牌

我有两个问题(可能是3个):

  1. 我究竟做错了什么?
  2. 是否可以使用我的Startup.Auth中的访问令牌来创建经过身份validation的Graph Client? 如果是这样,我该怎么做呢?

      // This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API. string graphResourceId = "https://graph.microsoft.com"; //https://graph.windows.net public void ConfigureAuth(IAppBuilder app) { ApplicationDbContext db = new ApplicationDbContext(); app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseKentorOwinCookieSaver(); app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = clientId, Authority = Authority, PostLogoutRedirectUri = postLogoutRedirectUri, Notifications = new OpenIdConnectAuthenticationNotifications() { // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away. AuthorizationCodeReceived = (context) => { var code = context.Code; ClientCredential credential = new ClientCredential(clientId, appKey); string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value; AuthenticationContext authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID)); AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode( code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId); HttpContext.Current.Cache.Add("Token", result.AccessToken, null, DateTime.Now.AddHours(5), Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null); return Task.FromResult(0); } } }); } } 

每条评论更新的代码如下

  public string GetTokenForApplication(string graphResourceID) { string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value; string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; string authority = "https://login.microsoftonline.com/" + tenantID; try { // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc) ClientCredential clientcred = new ClientCredential(clientId, appKey); // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database AuthenticationContext authenticationContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(userObjectID)); var result = authenticationContext.AcquireTokenSilent(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId)); return result.AccessToken; } catch (Exception e) { // Capture error for handling outside of catch block ErrorMessage = e.Message; return null; } } 

更新2:修复..种类

感谢@Fei Xue,我发现了问题..有点儿。 这在本地运行时解决了我的问题,但是在发布到我的舞台应用程序时仍然无法以静默方式获取令牌。当我第一次创建应用程序时,我包括了Azure AD的工作/学校身份validation。 这创建了一个用于ADAL令牌缓存的本地数据库上下文。 在开发应用程序时,我为我为应用程序创建的Azure SQL DB创建了另一个数据库上下文。 我不得不更新我的AdalTokenCache.cs以反映我的应用程序的DB上下文和新模型。 我更新了这一行:

 private ApplicationDbContext db = new ApplicationDbContext(); 

使用我自己的上下文并将UserTokenCache模型更新为我的新上下文的UserTokenCache模型。 在这种情况下我改变了:

 private UserTokenCache Cache; 

至:

 private UserTokenCach Cache; 

然后我更新了CS的其余部分以匹配应用程序的DB上下文中的UserTokenCach。

然后,我使用了UserProfile控制器中的OOB的AcquireToken方法来获取令牌。 这就是它看起来像的样子(注意:我还将我的startup.auth中的字符串从私有更新为public,因此我可以在我的viewmodel中使用它们):

  public string GetTokenForApplication(string graphResourceID) { string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value; string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; string authority = "https://login.microsoftonline.com/" + tenantID; try { // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc) ClientCredential clientcred = new ClientCredential(Startup.clientId, Startup.appKey); // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database AuthenticationContext authenticationContext = new AuthenticationContext(Startup.Authority, new ADALTokenCache(signedInUserID)); var result = authenticationContext.AcquireTokenSilent(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId)); return result.AccessToken; } catch (Exception e) { // Capture error for handling outside of catch block ErrorMessage = e.Message; return null; } } 

当我玩更多时,我会更新。

Azure Active Directory发布了两种访问令牌。

第一个是委托令牌 ,用于委托用户操作用户的资源。

另一个是应用程序令牌 ,通常用于对所有组织的资源执行操作,并且此令牌中没有用户上下文。 因此我们不应该使用此令牌来执行需要用户上下文的资源。

post中的代码是使用客户端凭证流获取访问令牌,该流是应用程序令牌 。 因此,当您根据用户的上下文使用此类令牌获取用户或图片时,您将收到错误。

在这种情况下,您应该在发布时使用AuthorizationCodeReceived事件获取访问令牌。 此事件使用授权代码授予流来获取用户的委托令牌 。 然后在控制器中,您可以使用AcquireTokenSilentAsync方法获取令牌,该方法将从catch获取访问令牌。

下面的代码示例对于在Web应用程序中调用Microsoft Graph以委派登录用户的方案非常有用:

主动目录的dotnet-graphapi的Web