c# - 在 MVC Web App 中通过 ViewModel 调用 Graph API

标签 c# .net asp.net-mvc microsoft-graph-api adal

我正在尝试使用 Graph API 在我的网络应用程序的导航栏中创建我自己的“用户配置文件”部分。为此,我对 UserProfile Controller 的 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);
            }
        });

Controller 将我的 UserProfileViewModel 作为 Json 返回,我用它来替换上述元素,如我的 AJAX 成功函数中所示。

用户配置文件 Controller :

    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;
    }

我已成功获取访问 token ,但我的 AJAX 调用未返回任何数据。
Access Token from IIS Log Console Log

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

  1. 我做错了什么?
  2. 是否可以使用访问 token 从我的 Startup.Auth 创建一个经过身份验证的 Graph 客户端?如果是这样, 我该怎么做呢?

        // 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 我解决了这个问题..有点。这解决了我在本地运行时的问题,但在发布到我的阶段应用程序时我仍然无法静默获取 token 。当我第一次创建应用程序时,我包含了 Azure AD 的工作/学校身份验证。这创建了一个用于 ADAL token 缓存的本地数据库上下文。在开发应用程序时,我为我为应用程序创建的 Azure SQL 数据库创建了另一个数据库上下文。我必须更新我的 AdalTokenCache.cs 以反射(reflect)我的应用程序的数据库上下文和新模型。我更新了行:

private ApplicationDbContext db = new ApplicationDbContext();

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

private UserTokenCache Cache;

到:

private UserTokenCach Cache;

然后我更新了 CS 的其余部分以匹配来自应用程序的数据库上下文的 UserTokenCach。

然后我只是使用 UserProfile Controller 中 OOB 中的 AcquireToken 方法来获取 token 。这就是它最终的样子(注意:我还将 startup.auth 中的字符串从私有(private)更新为公共(public),以便我可以在我的 View 模型中使用它们):

    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 颁发了两种访问 token 。

第一个是delegate-token,用于委托(delegate)用户操作用户的资源。

另外一个是application token,通常用于对所有组织的资源执行操作,这个token中没有用户上下文。所以我们不应该使用这个 token 来执行需要用户上下文的资源作为me

帖子中的代码是使用客户端凭据流获取访问 token ,即应用程序 token 。因此,当您使用这种基于用户上下文的 token 获取用户或图片时,您会出错。

在这种情况下,您应该在发布时使用 AuthorizationCodeReceived 事件获取访问 token 。此事件使用授权代码授予流程为用户获取delegate-token。然后在 Controller 中,您可以使用 AcquireTokenSilentAsync 方法获取 token ,该方法将从 catch 获取访问 token 。

下面的代码示例对于在 Web 应用中调用 Microsoft Graph 以委托(delegate)登录用户的场景非常有帮助:

active-directory-dotnet-graphapi-web

关于c# - 在 MVC Web App 中通过 ViewModel 调用 Graph API,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44722002/

相关文章:

c# - 如何根据 "is-subset"条件有效地修剪列表?

c# - 如何理解链接时是否复制或引用了对象

c# - 在 .NET 中捕获包括半透明窗口的屏幕截图

c# - NHibernate - 通过内部集合中的两个项目的标准获取类

c# - 使用 PostAsJsonAsync 调用 Web API

c# - 当源和目标都在同一个远程文件服务器上时,是否远程处理 File.Copy?

c# - 消失的系统托盘图标

c# - Platform.GetRenderer(view) 已过时

c# - 使用数据注释分配 DateTime 的格式?

asp.net-mvc - 如何检查是否使用post或get调用了 Controller ?