授权策略总是返回 403 (Forbidden) - MVC/API

Nic*_*las 9 c# azure jwt asp.net-identity asp.net-core

我创建了一个 API(带有 EF Core 的 .Net Core 2),带有一个端点来检索某些角色。我将 ASPNetIdentity 集成到我的项目中,并且我正在使用 AspNetRoles 和 AspNetRoleClaims。

在调用 API 时,用户在我的情况下具有特定的角色(管理员)。这个角色有几个角色声明。在 startup.cs 我为这个角色添加了策略:

   options.AddPolicy(
      "Create Roles", policy => policy.RequireClaim("Can create roles", "role.create"));
   options.AddPolicy(
      "View Roles", policy => policy.RequireClaim("Can read roles", "role.read"));
   options.AddPolicy(
      "Edit Roles", policy => policy.RequireClaim("Can update roles", "role.update"));
   options.AddPolicy(
      "Delete Roles", policy => policy.RequireClaim("Can delete roles", "role.delete"));
Run Code Online (Sandbox Code Playgroud)

在我的前端,用户可以使用他们的 Microsoft (azure) 帐户登录,并且他们的 oidc 声明 (ID) 与 AspNetUser 表中的 ID 匹配,如果在用户表中找不到他们的 oidc,则会自动添加他们(他们的 oidc id 为aspnetuser id),他们得到一个默认角色。

然而,在调用 Role 端点时,它总是返回 403 错误(禁止)。当我检查表时,用户具有访问端点的正确角色和角色声明。它怎么可能不断返回403?

端点如下所示:

[HttpGet]
[Authorize(Policy = "View Roles")]
public IEnumerable<IdentityRole> GetRole()
{
     return _context.Roles;
}
Run Code Online (Sandbox Code Playgroud)

经过一些研究,我发现了一个帖子,它告诉您需要在发送到 API 的令牌中包含用户的角色(声明),尽管这意味着我将需要一个首先返回角色的 GET 端点) 的用户,前端需要把它捡起来添加到令牌中,然后用令牌中包含的角色调用所有其他端点?还是我在这里走错了路?

- - 更新 - -

我 90% 确定策略/授权检查需要将角色声明包含在用户的令牌中。然而,现在的过程如下:

  1. 用户进入前端项目(react frontend)。
  2. 前端使用 adal.js 来检查用户是否通过身份验证,如果他没有通过身份验证,那么用户将被重定向到 Microsoft 登录页面。
  3. 登录成功后,正在调用API。
  4. 在 API 的 DI(AddJwtBearer)中,oid 声明正在与 ASpNetUsers 表的 ID 进行比较,如果它不存在,则使用 AspNetUser 的 ID 的 oid 值将用户添加到 AspNetUser 表中。

现在用户也被添加到 AspNetUser 表中,我可以使用 Asp.Net Identity 来使用 Roles 和 RoleClaims 进行授权。

然而,问题是 API 最初收到的令牌是 Azure 令牌,它对我的​​身份表一无所知(如果我错了,请纠正我)。我相信这也是我的政策不起作用的原因(如果我错了,再次纠正我)。

我发现了一个问题或多或少相同的帖子(https://joonasw.net/view/adding-custom-claims-aspnet-core-2),诀窍是使用我需要的身份声明扩展当前令牌比如 ClaimTypes.Role.

我实现这一目标的代码如下:

// Add authentication (Azure AD)
            services
                .AddAuthentication(sharedOptions =>
                {
                    sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    sharedOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 
                    sharedOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // Use JWT as ChallendgeSchema, if not, ASPNet Identity will be used by default and this will return a default non-existing endpoint (because it is not created): Account/Login; /sf/ask/3211471651/
                })
                .AddJwtBearer(options =>
                {
                    options.Audience = this.Configuration["AzureAd:ClientId"];
                    options.Authority = $"{this.Configuration["AzureAd:Instance"]}{this.Configuration["AzureAd:TenantId"]}";

                    // Added events which checks if the user (token user) exists in our own database, if not then the user is being added with a 'User' role
                    options.Events = new JwtBearerEvents()
                    {
                        OnTokenValidated = context =>
                        {
                            // Check if roles are present
                            CheckRoles cr = new CheckRoles();
                            cr.CreateRoles(services.BuildServiceProvider());

                            // Check if the user has an OID claim(oid = object id = user id)
                            if (!context.Principal.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier")) 
                            {
                                context.Fail($"The claim 'oid' is not present in the token.");
                            }

                            ClaimsPrincipal userPrincipal = context.Principal;

                            CheckUser cu = new CheckUser();

                            cu.CreateUser(userPrincipal, services.BuildServiceProvider());

                            // Extend the current token with my (test) Role claim
                            var claims = new List<Claim>
                            {
                                new Claim(ClaimTypes.Role, "Admin")
                            };
                            var appIdentity = new ClaimsIdentity(claims);
                            context.Principal.AddIdentity(appIdentity);


                            return Task.CompletedTask;
                        }
                    };
                });
Run Code Online (Sandbox Code Playgroud)

遗憾的是,上述方法不起作用,当从前端调用 API 时,令牌保持不变,并且没有添加 RoleClaim。有人知道如何将我的 RoleClaim 添加到令牌中以便我可以使用我的策略吗?

Win*_*Win 5

在调用 API 时,用户在我的情况下具有特定的角色(管理员)。这个角色有几个角色声明。

如果用户有role.read作为ClaimTypes.Role的主要对象,他们可以创建像下面的策略Startup.cs-

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddAuthorization(options =>
    {
        options.AddPolicy("View Roles", policyBuilder =>
        {
            policyBuilder.RequireAuthenticatedUser()
                .RequireAssertion(context =>
                    context.User.HasClaim(ClaimTypes.Role, "role.read"))
                .Build();
        });
    });
    ...
}
Run Code Online (Sandbox Code Playgroud)

更新

您需要为JwtBearerDefaults.AuthenticationScheme声明身份添加身份验证类型,以便它与默认方案匹配。

services
   .AddAuthentication(sharedOptions =>
   {
      ...
   })
   .AddJwtBearer(options =>
   {
      ...
      options.Events = new JwtBearerEvents()
      {         
         OnTokenValidated = context =>
         {
            ...
            var appIdentity = new ClaimsIdentity(claims, 
                   JwtBearerDefaults.AuthenticationScheme);
                               ^^^^^

            context.Principal.AddIdentity(appIdentity);

            return Task.CompletedTask;
         }
      };
   });
Run Code Online (Sandbox Code Playgroud)