Microsoft Graph Api returns 尝试在 ASP.NET Core MVC 中使用 /me/memberOf 时禁止响应
Microsoft Graph Api returns forbidden response when trying to use /me/memberOf in ASP.NET Core MVC
这是我的。 (API版本为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。我无法让它与应用程序特定权限一起使用(returns 禁止响应)。这是一个问题的原因是,为了同意委派的权限,它需要全局管理员。因此,我正在尝试执行仅限应用程序权限,以允许我同意整个组织。我意识到这与一些已知问题 https://graph.microsoft.io/en-us/docs/overview/release_notes 相当接近,但他们还列出了替代权限范围,我已经尝试了所有这些但绝对没有成功。 (注意:身份验证工作正常,其他请求正常工作)
有人可以给我一些见解吗?
好的,经过大量阅读和一些简单的运气,我明白了。所以,我想我会分享我学到的东西,因为它太令人困惑了。另外,我发现我在 azure 中缺少的权限在 Microsoft Graph 下:登录并读取用户配置文件....已在 windows azure 权限中检查,但我想它需要检查在 Microsoft Graph 权限中也...那是 User.Read 权限给那些弄乱清单的人...密切注意 GetUsersRoles Task 它已被注释以提供帮助,但您不能调用“/me/memberOf”,你必须调用“/users//memberOf”。我真的希望这对某人有所帮助,因为自从我开始将它添加到我的项目中以来,Api 每天都让我很头疼。
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
// 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();
}
}
}
}
请在此处再次阅读有关权限的 Microsoft Graph 主题:https://graph.microsoft.io/en-us/docs/authorization/permission_scopes。这里有几个概念可能有助于澄清问题(尽管我们的文档在这方面肯定可以改进):
- 有两种类型的权限:申请权限和委派权限
- 一些委派的权限可以得到最终用户的同意(通常当权限仅限于请求登录用户的数据时——比如他们的个人资料、他们的邮件、他们的文件。
- 提供访问 比登录用户范围更多 数据的其他委托权限通常需要管理员同意。
- 应用程序权限始终需要管理员同意。根据定义,这些是租户范围的(因为没有用户上下文)。
- 管理员可以代表组织同意委派权限(从而抑制最终用户的任何同意体验)。同样,还有更多可用的主题。
如果您始终有登录用户在场(看起来像),我强烈建议您使用委派权限而不是应用程序权限。
我还注意到您正在使用群组显示名称创建声明。组显示名称不是不可变的,可以更改...不确定如果应用程序根据这些声明的值做出授权决策,这 是否会 导致一些有趣的安全问题。
希望这对您有所帮助,
我们也在使用 AAD 进行身份验证,在我们的例子中,我们需要强制用户再次同意应用程序权限。
我们通过向 AAD 登录请求添加 prompt=consent
参数为单个用户解决了这个问题。 ADAL.js 这里有一个例子:
Microsoft Graph API - 403 Forbidden for v1.0/me/events
来自post的相关代码示例:
window.config = {
tenant: variables.azureAD,
clientId: variables.clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
graphApiUri: "https://graph.microsoft.com",
sharePointUri: "https://" + variables.sharePointTenant + ".sharepoint.com",
},
cacheLocation: "localStorage",
extraQueryParameter: "prompt=consent"
}
我遇到了类似的问题,只是我的令牌已过期或失效。
这是我的。 (API版本为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。我无法让它与应用程序特定权限一起使用(returns 禁止响应)。这是一个问题的原因是,为了同意委派的权限,它需要全局管理员。因此,我正在尝试执行仅限应用程序权限,以允许我同意整个组织。我意识到这与一些已知问题 https://graph.microsoft.io/en-us/docs/overview/release_notes 相当接近,但他们还列出了替代权限范围,我已经尝试了所有这些但绝对没有成功。 (注意:身份验证工作正常,其他请求正常工作)
有人可以给我一些见解吗?
好的,经过大量阅读和一些简单的运气,我明白了。所以,我想我会分享我学到的东西,因为它太令人困惑了。另外,我发现我在 azure 中缺少的权限在 Microsoft Graph 下:登录并读取用户配置文件....已在 windows azure 权限中检查,但我想它需要检查在 Microsoft Graph 权限中也...那是 User.Read 权限给那些弄乱清单的人...密切注意 GetUsersRoles Task 它已被注释以提供帮助,但您不能调用“/me/memberOf”,你必须调用“/users/
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
// 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();
}
}
}
}
请在此处再次阅读有关权限的 Microsoft Graph 主题:https://graph.microsoft.io/en-us/docs/authorization/permission_scopes。这里有几个概念可能有助于澄清问题(尽管我们的文档在这方面肯定可以改进):
- 有两种类型的权限:申请权限和委派权限
- 一些委派的权限可以得到最终用户的同意(通常当权限仅限于请求登录用户的数据时——比如他们的个人资料、他们的邮件、他们的文件。
- 提供访问 比登录用户范围更多 数据的其他委托权限通常需要管理员同意。
- 应用程序权限始终需要管理员同意。根据定义,这些是租户范围的(因为没有用户上下文)。
- 管理员可以代表组织同意委派权限(从而抑制最终用户的任何同意体验)。同样,还有更多可用的主题。
如果您始终有登录用户在场(看起来像),我强烈建议您使用委派权限而不是应用程序权限。
我还注意到您正在使用群组显示名称创建声明。组显示名称不是不可变的,可以更改...不确定如果应用程序根据这些声明的值做出授权决策,这 是否会 导致一些有趣的安全问题。
希望这对您有所帮助,
我们也在使用 AAD 进行身份验证,在我们的例子中,我们需要强制用户再次同意应用程序权限。
我们通过向 AAD 登录请求添加 prompt=consent
参数为单个用户解决了这个问题。 ADAL.js 这里有一个例子:
Microsoft Graph API - 403 Forbidden for v1.0/me/events
来自post的相关代码示例:
window.config = {
tenant: variables.azureAD,
clientId: variables.clientId,
postLogoutRedirectUri: window.location.origin,
endpoints: {
graphApiUri: "https://graph.microsoft.com",
sharePointUri: "https://" + variables.sharePointTenant + ".sharepoint.com",
},
cacheLocation: "localStorage",
extraQueryParameter: "prompt=consent"
}
我遇到了类似的问题,只是我的令牌已过期或失效。