通过MVC Web App中的ViewModel调用Graph API [英] Calling Graph API through ViewModel in MVC Web App

查看:80
本文介绍了通过MVC Web App中的ViewModel调用Graph API的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用Graph API在Web应用程序的导航栏中创建自己的用户个人资料"部分.为此,我需要对UserProfile Controller的GetUser操作进行AJAX调用:

I'm trying to use Graph API to create my own "User Profile" section of the navbar of my web app. To do this I have an AJAX call to a GetUser Action of my UserProfile Controller:

        $.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成功函数中所示.

The controller returns my UserProfileViewModel as a Json which I use to replace the above elements as shown in my AJAX success function.

UserProfile控制器:

UserProfile Controller:

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

我的UserProfileViewModel看起来像这样:

My UserProfileViewModel looks like this:

    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日志访问访问令牌 控制台日志

I am successfully getting an access token, however my AJAX call is not returning any data.
Access Token from IIS Log Console Log

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

I have two questions (possibly 3):

  1. 我在做什么错?
  2. 是否可以使用访问令牌 从我的Startup.Auth创建一个经过身份验证的Graph Client?如果是这样, 我将如何去做?

  1. What am I doing wrong?
  2. Is it possible to use the access token from my Startup.Auth to create an authenticated Graph Client? If so, how would I go about doing that?

    // 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).这将创建用于ADAL令牌缓存的本地数据库上下文.在开发应用程序时,我为我为该应用程序创建的Azure SQL数据库创建了另一个数据库上下文.我必须更新我的AdalTokenCache.cs以反映我的应用程序的数据库上下文和新模型.我更新了这一行:

Thanks to @Fei Xue I figured out the problem.. kind of. This fixes my problem when running locally, but I still fail to acquire the token silently when publishing to my stage application.. When I first created the application, I included Work/School authentication that was Azure AD. This created a local DB Context that it used for the ADAL token cache. While developing the application, I created another DB Context for the Azure SQL DB I created for the app. I had to update my AdalTokenCache.cs to reflect my app's DB context and the new model. I updated the line:

private ApplicationDbContext db = new ApplicationDbContext();

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

with my own context and updated the UserTokenCache model to my new context's UserTokenCache model. In this case I changed:

private UserTokenCache Cache;

收件人:

private UserTokenCach Cache;

然后我更新了其余CS,以匹配应用程序数据库上下文中的UserTokenCach.

I then updated the rest of the CS to match the UserTokenCach from the app's DB context.

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

I then just used the AcquireToken method that came OOB in the UserProfile controller to get a token. This is what it wound up looking like (Note: I also updated the strings in my startup.auth from private to public so I could use them in my 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;
        }

    }

我会随着其他游戏的更新而更新.

I'll update as I play around some more.

推荐答案

Azure Active Directory发出两种访问令牌.

There are two kinds of access token issued by Azure Active Directory.

第一个是 delegate-token (代理令牌),它用于委派用户来操作用户的资源.

The first one is delegate-token which used to delegate the user to operate user's resource.

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

And the other one is application token which usually used to perform the operation for the resource of all organization and there is no user context in this token. So we shouldn't use this token to perform the resource as me which required the user context.

帖子中的代码是使用客户端凭据流(应用程序令牌)获取访问令牌的.因此,当您根据用户的上下文使用这种令牌来获取用户或图片时,就会出错.

The code in the post is acquire the access token using client credentials flow which is application token. So you will get error when you get the user or picture using this kind of token based on the user's context.

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

In this scenario, you should acquire the access token using the AuthorizationCodeReceived event as you post. This event uses the authorization code grant flow to acquire the delegate-token for the user. Then in the controller, you can get the token using the method AcquireTokenSilentAsync which will get the access token from catch.

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

The code sample below is very helpful for the scenario calling Microsoft Graph in a web app to delegate the sign-in user:

active-directory-dotnet-graphapi-web

这篇关于通过MVC Web App中的ViewModel调用Graph API的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆