c#之授权策略始终返回 403(禁止)-MVC/API

Demo 阅读:118 2024-05-10 16:29:43 评论:0

我创建了一个 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")); 

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

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

端点如下所示:

[HttpGet] 
[Authorize(Policy = "View Roles")] 
public IEnumerable<IdentityRole> GetRole() 
{ 
     return _context.Roles; 
} 
经过一番研究,我发现一篇文章告诉您需要在发送到 API 的 token 中包含用户的角色(声明),但这意味着我需要一个首先返回用户的角色,前端需要拾取它并将其添加到 token 中,然后使用 token 中包含的角色调用所有其他端点?或者我在这里走错了路?

----更新----

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

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

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

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

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

我实现此目的的代码如下:

// 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; https://stackoverflow.com/questions/45878166/asp-net-core-2-0-disable-automatic-challenge 
                }) 
                .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; 
                        } 
                    }; 
                }); 

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

请您参考如下方法:

When calling the API the user has a specific role (Admin) in my case. This role has a few role claims.

如果用户在主体对象中将 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(); 
        }); 
    }); 
    ... 
} 

更新

您需要将 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; 
         } 
      }; 
   }); 


标签:C#
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

关注我们

一个IT知识分享的公众号