在Azure Active Directory B2C中按组授权

Gre*_*aue 25 asp.net-mvc azure azure-ad-b2c

我试图弄清楚如何授权使用Azure Active Directory B2C中的组.我可以通过用户授权,例如:

[Authorize(Users="Bill")]
Run Code Online (Sandbox Code Playgroud)

然而,这不是很有效,我看到很少用例.另一种解决方案是通过角色授权.但是由于某些原因,似乎并没有wowrk.例如,如果我为用户提供"全局管理员"角色,请尝试:

[Authorize(Roles="Global Admin")]
Run Code Online (Sandbox Code Playgroud)

这是行不通的.有没有办法通过群组或角色进行授权?

小智 42

从Azure AD获取用户的组成员资格需要的不仅仅是"几行代码",所以我想我会分享最终为我工作的东西,以便为其他人节省几天的头发和头部 - 敲打.

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

"dependencies": {
    ...
    "Microsoft.IdentityModel.Clients.ActiveDirectory": "3.13.8",
    "Microsoft.Azure.ActiveDirectory.GraphClient": "2.0.2"
}
Run Code Online (Sandbox Code Playgroud)

第一个是必要的,因为我们需要验证我们的应用程序,以便它能够访问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));
Run Code Online (Sandbox Code Playgroud)

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

当客户端需要获取身份验证令牌时,将根据需要调用我们传递给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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,它具有用于处理瞬态条件的内置重试机制,您可能希望根据应用程序的需要进行定制.

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

new OpenIdConnectOptions()
{
    ...         
    Events = new OpenIdConnectEvents()
    {
        ...
        OnTokenValidated = SecurityTokenValidated
    },
};
Run Code Online (Sandbox Code Playgroud)

当已获取,验证并建立用户身份的登录用户的访问令牌时,将触发该事件.(不要与调用AAD Graph API所需的应用程序自己的访问令牌混淆!)它看起来像是一个用于查询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);
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

这里使用的是角色声明类型,但您可以使用自定义声明类型.

完成上述操作后,如果您使用的是ClaimType.Role,那么您需要做的就是装饰您的控制器类或方法,如下所示:

[Authorize(Role = "Administrators")]
Run Code Online (Sandbox Code Playgroud)

也就是说,如果您在B2C中配置了指定的组,其显示名称为"Administrators".

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

services.AddAuthorization(options => options.AddPolicy("ADMIN_ONLY", policy => policy.RequireClaim("<your_custom_claim_type>", "Administrators")));
Run Code Online (Sandbox Code Playgroud)

然后按如下方式装饰特权控制器类或方法:

[Authorize(Policy = "ADMIN_ONLY")]
Run Code Online (Sandbox Code Playgroud)

好的,我们完成了吗? - 嗯,不完全是这样.

如果您运行了应用程序并尝试登录,则会从Graph API中获得一个例外,声称"没有足够的权限来完成操作".这可能并不明显,但是当您的应用程序使用其app_id和app_key成功通过AD进行身份验证时,它没有从AD读取用户详细信息所需的权限.为了授予应用程序这种访问权限,我选择使用Azure Active Directory模块进行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
Run Code Online (Sandbox Code Playgroud)

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

  • 这是一篇杰出的文章.谢谢! (2认同)

ast*_*kov 18

这将有效,但是您在身份验证逻辑中编写了几行代码,以实现您的目标.

首先,您必须区分Azure AD(B2C)RolesGroupsAzure AD(B2C).

User Role是非常具体的,仅在Azure AD(B2C)本身有效.角色定义了Azure AD中用户具有的权限.

Group(或Security Group)定义用户组成员资格,可以向外部应用程序公开.外部应用程序可以在安全组之上建模基于角色的访问控制.是的,我知道这听起来有点令人困惑,但事实就是如此.

因此,您的第一步是Groups在Azure AD B2C中建模- 您必须创建组并手动将用户分配给这些组.您可以在Azure门户(https://portal.azure.com/)中执行此操作:

在此输入图像描述

然后,回到您的应用程序,您将不得不编写一些代码,并在用户成功通过身份验证后向Azure AD B2C Graph API询问用户成员身份.您可以使用此示例来获取有关如何获取用户组成员身份的启发.最好在其中一个OpenID通知(即SecurityTokenValidated)中执行此代码,并将用户角色添加到ClaimsPrincipal.

将ClaimsPrincipal更改为具有Azure AD安全组和"角色声明"值后,您将能够使用具有角色功能的Authrize属性.这实际上是5-6行代码.

最后,您可以在此处对该功能进行投票:https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/10123836-get-user-membership-groups-in-the-claims- with-ad-b以获取组成员身份声明,而无需为此查询Graph API.

  • 你能不能展示那 5-6 行?几天来,我一直在努力拼凑这个问题的答案,我已经写了 100 多行代码(而且它也没有工作!)。如果只需 5 或 6 行即可轻松连接通知、查询用户组数据的图表,并将组添加到 ClaimsPrincipal 角色,那么我显然是在挑错树。我真的很感激一些重定向! (2认同)
  • 如何访问“Azure B2C 设置”?我发现没有地方可以将组添加到 Azure B2C 租户,但奇怪的是,我可以将用户添加到组(即使不存在组)。 (2认同)

wha*_*ava 5

我以书面形式实现了这一目标,但截至2017年5月,

((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName, ClaimValueTypes.String));
Run Code Online (Sandbox Code Playgroud)

需要更改为

((ClaimsIdentity)context.Ticket.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, group.DisplayName));
Run Code Online (Sandbox Code Playgroud)

使它与最新的库一起使用

作者的伟大作品

另外,如果您在Connect-MsolService遇到问题,将错误的用户名和密码更新为最新的lib