c# - 在 Azure Active Directory B2C 中按组授权

标签 c# asp.net-mvc azure azure-ad-b2c

我正在尝试弄清楚如何在 Azure Active Directory B2C 中使用组进行授权。我可以通过用户授权,例如:

[Authorize(Users="Bill")]

但是,这不是很有效,而且我看到的用例很少。另一种解决方案是通过角色授权。但由于某种原因,这似乎不起作用。例如,如果我为用户授予“全局管理员”角色,并尝试:

[Authorize(Roles="Global Admin")]

有没有办法通过组或角色进行授权?

最佳答案

从 Azure AD 获取用户的组成员资格需要的不仅仅是“几行代码”,因此我想分享一下最终对我有用的内容,以节省其他人几天的麻烦并用头撞击。

让我们首先将以下依赖项添加到project.json:

"dependencies": {
    ...
    "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.8",
    "Microsoft.Azure.ActiveDirectory.GraphClient": "2.0.2"
}

第一个是必要的,因为我们需要对我们的应用程序进行身份验证,以便它能够访问 AAD Graph API。 第二个是我们将用来查询用户成员资格的 Graph API 客户端库。 不用说,这些版本仅在撰写本文时有效,并且将来可能会发生变化。

接下来,在 Startup 类的 Configure() 方法中,也许就在我们配置 OpenID Connect 身份验证之前,我们创建 Graph API 客户端,如下所示:

var authContext = new AuthenticationContext("https://login.microsoftonline.com/<your_directory_name>.onmicrosoft.com");
var clientCredential = new ClientCredential("<your_b2c_app_id>", "<your_b2c_secret_app_key>");
const string AAD_GRAPH_URI = "https://graph.windows.net";
var graphUri = new Uri(AAD_GRAPH_URI);
var serviceRoot = new Uri(graphUri, "<your_directory_name>.onmicrosoft.com");
this.aadClient = new ActiveDirectoryClient(serviceRoot, async () => await AcquireGraphAPIAccessToken(AAD_GRAPH_URI, authContext, clientCredential));

警告:请勿对您的 secret 应用程序 key 进行硬编码,而应将其保存在安全的地方。嗯,你已经知道了,对吧? :)

当客户端需要获取身份验证 token 时,我们将根据需要调用我们传递给 AD 客户端构造函数的异步 AcquireGraphAPIAccessToken() 方法。该方法如下所示:

private async Task<string> AcquireGraphAPIAccessToken(string graphAPIUrl, AuthenticationContext authContext, ClientCredential clientCredential)
{
    AuthenticationResult result = null;
    var retryCount = 0;
    var retry = false;

    do
    {
        retry = false;
        try
        {
            // ADAL includes an in-memory cache, so this will only send a request if the cached token has expired
            result = await authContext.AcquireTokenAsync(graphAPIUrl, clientCredential);
        }
        catch (AdalException ex)
        {
            if (ex.ErrorCode == "temporarily_unavailable")
            {
                retry = true;
                retryCount++;
                await Task.Delay(3000);
            }
        }
    } while (retry && (retryCount < 3));

    if (result != null)
    {
        return result.AccessToken;
    }

    return null;
}

请注意,它有一个内置的重试机制来处理 transient 条件,您可能需要根据应用程序的需求进行定制。

现在我们已经处理了应用程序身份验证和 AD 客户端设置,我们可以继续利用 OpenIdConnect 事件来最终使用它。 回到我们通常调用 app.UseOpenIdConnectAuthentication() 并创建 OpenIdConnectOptions 实例的 Configure() 方法,我们为 OnTokenValidated 事件添加一个事件处理程序:

new OpenIdConnectOptions()
{
    ...         
    Events = new OpenIdConnectEvents()
    {
        ...
        OnTokenValidated = SecurityTokenValidated
    },
};

当获取、验证登录用户的访问 token 并建立用户身份时,将触发该事件。 (不要与调用 AAD Graph API 所需的应用程序自己的访问 token 混淆!) 它看起来是一个在 Graph API 中查询用户组成员身份并以附加声明的形式将这些组添加到身份中的好地方:

private Task SecurityTokenValidated(TokenValidatedContext context)
{
    return Task.Run(async () =>
    {
        var oidClaim = context.SecurityToken.Claims.FirstOrDefault(c => c.Type == "oid");
        if (!string.IsNullOrWhiteSpace(oidClaim?.Value))
        {
            var pagedCollection = await this.aadClient.Users.GetByObjectId(oidClaim.Value).MemberOf.ExecuteAsync();

            do
            {
                var directoryObjects = pagedCollection.CurrentPage.ToList();
                foreach (var directoryObject in directoryObjects)
                {
                    var group = directoryObject as Group;
                    if (group != null)
                    {
                        ((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String));
                    }
                }
                pagedCollection = pagedCollection.MorePagesAvailable ? await pagedCollection.GetNextPageAsync() : null;
            }
            while (pagedCollection != null);
        }
    });
}

此处使用的是角色声明类型,但是您可以使用自定义类型。

完成上述操作后,如果您使用 ClaimType.Role,您所需要做的就是像这样装饰您的 Controller 类或方法:

[Authorize(Role = "Administrators")]

当然,前提是您在 B2C 中配置了一个显示名称为“管理员”的指定组。

但是,如果您选择使用自定义声明类型,则需要通过在 ConfigureServices() 方法中添加类似内容来基于声明类型定义授权策略,例如:

services.AddAuthorization(options => options.AddPolicy("ADMIN_ONLY", policy => policy.RequireClaim("<your_custom_claim_type>", "Administrators")));

然后按如下方式装饰特权 Controller 类或方法:

[Authorize(Policy = "ADMIN_ONLY")]

好的,我们完成了吗? - 嗯,不完全是。

如果您运行应用程序并尝试登录,您会收到 Graph API 的异常,声称“权限不足,无法完成操作”。 这可能并不明显,但虽然您的应用程序使用其 app_id 和 app_key 成功通过 AD 进行身份验证,但它没有从 AD 读取用户详细信息所需的权限。 为了授予应用程序此类访问权限,我选择使用 Azure Active Directory Module for PowerShell

以下脚本对我有用:

$tenantGuid = "<your_tenant_GUID>"
$appID = "<your_app_id>"

$userVal = "<admin_user>@<your_AD>.onmicrosoft.com"
$pass = "<admin password in clear text>"
$Creds = New-Object System.Management.Automation.PsCredential($userVal, (ConvertTo-SecureString $pass -AsPlainText -Force))

Connect-MSOLSERVICE -Credential $Creds
$msSP = Get-MsolServicePrincipal -AppPrincipalId $appID -TenantID $tenantGuid

$objectId = $msSP.ObjectId

Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId $objectId

现在我们终于完成了! “几行代码”怎么样? :)

关于c# - 在 Azure Active Directory B2C 中按组授权,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40302231/

相关文章:

c# - 在 VS2010 的 .Net 2.0 项目中使用托管 C++ (.Net 2.0) 时出现问题

asp.net-mvc - 类型 'System.Web.Mvc.MvcWebRazorHostFactory' 的表达式不能用于返回类型 'System.Web.WebPages.Razor.WebRazorHostFactory'

asp.net-mvc - 如何在重定向到login.aspx后保留url中的参数

asp.net-mvc - ASP.NET MVC : convention for organizing ViewModels

azure - 分布式交换操作<ShuffleMoveOperation>

C#-我想在我的 wpf 应用程序中添加帮助文件?

c# - 通过DataGridView向集合添加元素

c# - 验证码图片验证: in C#.net和asp.net

python - 有没有办法在 EventHubConsumerClient (azure-sdk-for-python) 中的 on_event 函数上传递额外的参数

azure - 如何从 Azure 应用程序连接到本地 AD