尝试在 ASP.NET Core MVC 中使用/me/memberOf 时,Microsoft Graph Api 返回禁止响应

Microsoft Graph Api returns forbidden response when trying to use /me/memberOf in ASP.NET Core MVC(尝试在 ASP.NET Core MVC 中使用/me/memberOf 时,Microsoft Graph Api 返回禁止响应)

本文介绍了尝试在 ASP.NET Core MVC 中使用/me/memberOf 时,Microsoft Graph Api 返回禁止响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这就是我所拥有的.(ApiVersion 为 v1.0)

Here is what I have. (ApiVersion is v1.0)

private async Task<ClaimsIdentity> GetUsersRoles(string accessToken, ClaimsIdentity identity, string userId)
{
           string resource = GraphResourceId + ApiVersion + "/me/memberOf";

            var client = new HttpClient();

            var request = new HttpRequestMessage(HttpMethod.Get, new Uri(resource));

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            var response = await client.SendAsync(request);

        return identity;
    }

基本上我要做的是获取经过身份验证的用户所属的所有组,然后我从中创建组和角色声明.我在上面留下了一些,但代码在那里,它与以下委派权限 User.Read.All 和 Directory.Read.All 一起使用.我无法使用特定于应用程序的权限(返回禁止响应).这是一个问题的原因是,为了同意委派的权限,它需要全局管理员.因此,我正在尝试执行仅限应用程序的权限,以允许我同意整个组织.我意识到这与一些已知问题非常接近 https://graph.microsoft.io/en-us/docs/overview/release_notes ,但他们也列出了替代的权限范围,我已经尝试了所有这些都没有成功.(注意:身份验证工作正常,其他请求正常工作)

Essentially what I am attempting to do is get all of the groups that the authenticated user is a member of, then I am creating group and role claims from that. I have left some of that out above, but the code is there and it works with the following delegated permissions User.Read.All and Directory.Read.All. I can not get it to work with Application Specific Permissions (returns Forbidden response). The reason this is a problem is because, in order to consent to the delegated permissions, it requires Global Administrator. So, I am trying to do App Only permissions to allow me to consent for the entire organization. I realize that this is fairly close to some known issues https://graph.microsoft.io/en-us/docs/overview/release_notes , but they also list alternative permission scopes, and I have tried all of those with absolutely no success. (Note: Authentication works just fine and other requests work as they should)

有人能给我一些见解吗?

Could someone please give me some insight into this?

推荐答案

好的,经过大量阅读和一些简单的运气,我想通了.所以,我想我会分享我学到的东西,因为它是如此令人困惑.另外,我发现我在 azure 中缺少的权限在 Microsoft Graph 下:登录和读取用户配置文件....在 windows azure 权限中进行了检查,但我想它需要在 Microsoft Graph 中进行检查权限也...这是与清单混淆的人的 User.Read 权限...密切注意 GetUsersRoles 任务,它已被评论以提供帮助,但您不能调用/me/memberOf",您必须调用/users//memberOf".我真的希望这对某人有所帮助,因为自从我开始将它添加到我的项目中以来,这个 Api 每天都让我头疼.

Ok after a bunch of reading and some just plain Luck, I have this figured out. So, I figured I would share what I have learned since it is so confusing. Also, I found out that the permission that I was missing in azure was under Microsoft Graph : the Sign in and Read User profile.... which was checked in the windows azure permissions but I guess it needs to be checked in the Microsoft Graph permissions also... That is the User.Read permission for the people that mess with the manifest... Pay close attention to the GetUsersRoles Task it has been commented to help out, but you can't call "/me/memberOf", you have to call "/users/< userId >/memberOf". I really hope this helps somebody, because this Api has given me a headache everyday since I started adding it to my project.

Startup.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication;
using MyApp.Utils;
using Microsoft.Graph;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;

namespace MyApp
{
    public class Startup
    {
        public static string ClientId;
        public static string ClientSecret;
        public static string Authority;
        public static string GraphResourceId;
        public static string ApiVersion;
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

            if (env.IsDevelopment())
            {
                // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
                builder.AddUserSecrets();
            }
            builder.AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add Session services
            services.AddSession();

            // Add Auth
            services.AddAuthentication(
                SharedOptions => SharedOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme);

            services.AddMvc(config =>
            {
                var policy = new AuthorizationPolicyBuilder()
                                .RequireAuthenticatedUser()
                                .Build();

                config.Filters.Add(new AuthorizeFilter(policy));
            });

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            // Configure session middleware.
            app.UseSession();

            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            // Populate AzureAd Configuration Values 

            ClientId = Configuration["AzureAd:ClientId"];
            ClientSecret = Configuration["AzureAd:ClientSecret"];
            GraphResourceId = Configuration["AzureAd:GraphResourceId"];
            Authority = Configuration["AzureAd:AadInstance"] + Configuration["AzureAd:TenantId"];
            ApiVersion = Configuration["AzureAd:ApiVersion"];

            // Implement Cookie Middleware For OpenId
            app.UseCookieAuthentication();
            // Set up the OpenId options
            app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
            {
                ClientId = Configuration["AzureAd:ClientId"],
                ClientSecret = Configuration["AzureAd:ClientSecret"],
                Authority = Configuration["AzureAd:AadInstance"] + Configuration["AzureAd:TenantId"],
                CallbackPath = Configuration["AzureAd:CallbackPath"],
                ResponseType = OpenIdConnectResponseType.CodeIdToken,
                Events = new OpenIdConnectEvents
                {
                    OnRemoteFailure = OnAuthenticationFailed,
                    OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
                },

                TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    NameClaimType = "name",
                },
                GetClaimsFromUserInfoEndpoint = true,
                SaveTokens = true
            });

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });

        }

        private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
        {
            // Acquire a Token for the Graph API and cache it using ADAL.
            string userObjectId = (context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
            ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);

            // Gets Authentication Tokens From Azure
            AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId, context.HttpContext.Session));

            // Gets the Access Token To Graph API
            AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
                context.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, GraphResourceId);

            // Gets the Access Token for Application Only Permissions
            AuthenticationResult clientAuthResult = await authContext.AcquireTokenAsync(GraphResourceId, clientCred);

            // The user's unique identifier from the signin event
            string userId = authResult.UserInfo.UniqueId;

            // Get the users roles and groups from the Graph Api. Then return the roles and groups in a new identity
            ClaimsIdentity identity = await GetUsersRoles(clientAuthResult.AccessToken, userId);

            // Add the roles to the Principal User
            context.Ticket.Principal.AddIdentity(identity);

            // Notify the OIDC middleware that we already took care of code redemption.
            context.HandleCodeRedemption();
        }

        // Handle sign-in errors differently than generic errors.
        private Task OnAuthenticationFailed(FailureContext context)
        {
            context.HandleResponse();

            context.Response.Redirect("/Home/Error?message=" + context.Failure.Message);
            return Task.FromResult(0);
        }

        // Get user's roles as the Application
        /// <summary>
        /// Returns user's roles and groups as a ClaimsIdentity
        /// </summary>
        /// <param name="accessToken">accessToken retrieved using the client credentials and the resource (Hint: NOT the accessToken from the signin event)</param>
        /// <param name="userId">The user's unique identifier from the signin event</param>
        /// <returns>ClaimsIdentity</returns>
        private async Task<ClaimsIdentity> GetUsersRoles(string accessToken, string userId)
        {
            ClaimsIdentity identity = new ClaimsIdentity("LocalIds");

            var serializer = new Serializer();

            string resource = GraphResourceId + ApiVersion + "/users/" + userId + "/memberOf";

            var client = new HttpClient();

            var request = new HttpRequestMessage(HttpMethod.Get, new Uri(resource));

            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                var responseString = await response.Content.ReadAsStringAsync();

                var claims = new List<Claim>();

                var responseClaims = serializer.DeserializeObject<Microsoft.Graph.UserMemberOfCollectionWithReferencesResponse>(responseString);
                if (responseClaims.Value != null)
                {
                    foreach (var item in responseClaims.Value)
                    {
                        if (item.ODataType == "#microsoft.graph.group")
                        {
                            // Serialize the Directory Object
                            var gr = serializer.SerializeObject(item);
                            // Deserialize into a Group
                            var group = serializer.DeserializeObject<Microsoft.Graph.Group>(gr);
                            if (group.SecurityEnabled == true)
                            {
                                claims.Add(new Claim(ClaimTypes.Role, group.DisplayName));
                            }
                            else
                            {
                                claims.Add(new Claim("group", group.DisplayName));
                            }
                        }
                    }
                }
                identity.AddClaims(claims);
            }
            return identity;
        }

    }
}

NaiveSessionCache.cs

NaiveSessionCache.cs

// This is actually in a directory named Utils

using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace MyApp.Utils
{
    public class NaiveSessionCache : TokenCache
    {
        private static readonly object FileLock = new object();
        string UserObjectId = string.Empty;
        string CacheId = string.Empty;
        ISession Session = null;

        public NaiveSessionCache(string userId, ISession session)
        {
            UserObjectId = userId;
            CacheId = UserObjectId + "_TokenCache";
            Session = session;
            this.AfterAccess = AfterAccessNotification;
            this.BeforeAccess = BeforeAccessNotification;
            Load();
        }

        public void Load()
        {
            lock (FileLock)
            {
                Deserialize(Session.Get(CacheId));

            }
        }

        public void Persist()
        {
            lock (FileLock)
            {
                // reflect changes in the persistent store
                Session.Set(CacheId, this.Serialize());
                // once the write operation took place, restore the HasStateChanged bit to false
                this.HasStateChanged = false;
            }
        }

        // Empties the persistent store.
        public override void Clear()
        {
            base.Clear();
            Session.Remove(CacheId);
        }

        public override void DeleteItem(TokenCacheItem item)
        {
            base.DeleteItem(item);
            Persist();
        }

        // Triggered right before ADAL needs to access the cache.
        // Reload the cache from the persistent store in case it changed since the last access.
        void BeforeAccessNotification(TokenCacheNotificationArgs args)
        {
            Load();
        }

        // Triggered right after ADAL accessed the cache.
        void AfterAccessNotification(TokenCacheNotificationArgs args)
        {
            // if the access operation resulted in a cache update
            if (this.HasStateChanged)
            {
                Persist();
            }
        }
    }
}

这篇关于尝试在 ASP.NET Core MVC 中使用/me/memberOf 时,Microsoft Graph Api 返回禁止响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:尝试在 ASP.NET Core MVC 中使用/me/memberOf 时,Microsoft Graph Api 返回禁止响应

基础教程推荐