我们正在开发一个使用在.Net Core 2.2 Web API上构建的后端的应用程序。我们的大多数控制器仅需要[Authorize]
属性,而未指定任何策略。但是,某些终结点将要求用户位于特定的Azure AD安全组中。对于这些情况,我在Startup.cs文件中实现了以下策略:
var name = "PolicyNameIndicatingGroup";
var id = Guid.NewGuid; // Actually, this is set to the object ID of the group in AD.
services.AddAuthorization(
options =>
{
options.AddPolicy(
name,
policyBuilder => policyBuilder.RequireClaim(
"groups",
id.ToString()));
});
然后,在需要这种授权的控制器上,我有:
[Authorize("PolicyNameIndicatingGroup")]
public async Task<ResponseBase<string>> GroupProtectedControllerMethod() {}
问题在于我们的用户都在大量的组中。这将导致Graph API完全不返回任何组声明,而是将一个简单的hasGroups
布尔声明设置为true。因此,没有人具有任何个组,因此无法通过授权。可以阅读有关here的无组问题。
这种基于字符串的策略注册,尽管可能看起来很乏味,但这似乎是.Net Core人们所建议的,但是如果未在用户声明中填充组,则该注册将保持平稳。我没有真正看到如何解决这个问题。 是否有一些特殊的方法可以为我的API设置AppRegistration,以便 获得用户声明中填充的所有组?
更新:
在解决方案中,我确实有一个服务,该服务调用Graph以获取用户的组。但是,在为时已晚之前,我不知道如何调用它。换句话说,当用户点击控制器上的AuthorizeAttribute来检查策略时,尚未填充用户的组,因此受保护的方法始终使用403阻止它们。
我的尝试包括为所有Web API控制器制作一个自定义基本控制器。在基本控制器的构造函数中,我正在调用一个检查User.Identity
(类型为ClaimsIdentity
)的方法,以查看它是否已创建并通过身份验证,如果是,则使用{{ 1}}方法来填充用户组,这是从我的Graph调用中检索到的。但是,在输入基本控制器的构造函数时,尚未设置ClaimsIdentity.AddClaim(Claim claim)
,因此不会如前所述填充组。不知何故,在构造控制器之前,我需要填充用户的组。
答案 0 :(得分:0)
您可以改为使用Microsoft Graph API查询用户的组:
POST https://graph.microsoft.com/v1.0/directoryObjects/{object-id}/getMemberGroups
Content-type: application/json
{
"securityEnabledOnly": true
}
场景将是:
此协议article中使用Azure AD V2.0端点列出了协议图和示例请求。 This article用于V1.0端点。 Here是.Net Core的代码示例。
答案 1 :(得分:0)
由于ASP.NET Core团队的某人提供了一些技巧,因此我找到了该解决方案的答案。此解决方案涉及实现IClaimsTransformation
(在Microsoft.AspNetCore.Authentication
名称空间中)。引用我的来源:
[{
IClaimsTransformation
]是一项服务,您可以将其连接到请求管道中,该服务将在每次身份验证后运行,您可以根据需要使用它来扩展身份。那将是您进行Graph API调用的地方[...]。
因此,我编写了以下实现(请参见代码下方的重要说明):
public class AdGroupClaimsTransformer : IClaimsTransformation
{
private const string AdGroupsAddedClaimType = "adGroupsAlreadyAdded";
private const string ObjectIdClaimType = "http://schemas.microsoft.com/identity/claims/objectidentifier";
private readonly IGraphService _graphService; // My service for querying Graph
private readonly ISecurityService _securityService; // My service for querying custom security information for the application
public AdGroupClaimsTransformer(IGraphService graphService, ISecurityService securityService)
{
_graphService = graphService;
_securityService = securityService;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var claimsIdentity = principal.Identity as ClaimsIdentity;
var userIdentifier = FindClaimByType(claimsIdentity, ObjectIdClaimType);
var alreadyAdded = AdGroupsAlreadyAdded(claimsIdentity);
if (claimsIdentity == null || userIdentifier == null || alreadyAdded)
{
return Task.FromResult(principal);
}
var userSecurityGroups = _graphService.GetSecurityGroupsByUserId(userIdentifier).Result;
var allSecurityGroupModels = _securityService.GetSecurityGroups().Result.ToList();
foreach (var group in userSecurityGroups)
{
var groupIdentifier = allSecurityGroupModels.Single(m => m.GroupName == group).GroupGuid.ToString();
claimsIdentity.AddClaim(new Claim("groups", groupIdentifier));
}
claimsIdentity.AddClaim(new Claim(AdGroupsAddedClaimType, "true"));
return Task.FromResult(principal);
}
private static string FindClaimByType(ClaimsIdentity claimsIdentity, string claimType)
{
return claimsIdentity?.Claims?.FirstOrDefault(c => c.Type.Equals(claimType, StringComparison.Ordinal))
?.Value;
}
private static bool AdGroupsAlreadyAdded(ClaimsIdentity claimsIdentity)
{
var alreadyAdded = FindClaimByType(claimsIdentity, AdGroupsAddedClaimType);
var parsedSucceeded = bool.TryParse(alreadyAdded, out var valueWasTrue);
return parsedSucceeded && valueWasTrue;
}
}
在Startup.cs中的ConfigureServices
方法中,我这样注册实现:
services.AddTransient<IClaimsTransformation, AdGroupClaimsTransformer>();
警告
您可能已经注意到,我的实现是为防御目的而编写的,以确保不会在已经执行了该过程的ClaimsPrincipal上第二次运行该转换。这里的潜在问题是对IClaimsTransformation
的调用可能会发生多次,并且在某些情况下可能是不好的。您可以了解有关此here的更多信息。