自定义授权属性不允许在 asp.net 核心 3 中授权

Custom Authorization attribute doesn't allow authorize in asp.net core 3

.Net 核心应用程序使用 IdentityServer 进行身份验证。我在没有 https 的情况下工作。

我创建了自定义授权属性,但它不允许授权。而且我找不到问题。

日志:

dbug: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[8]
      AuthenticationScheme: Cookies was successfully authenticated.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Debug: AuthenticationScheme: Cookies was successfully authenticated.
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
      Authorization failed.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService: Information: Authorization failed.
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[13]
      AuthenticationScheme: Cookies was forbidden.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler: Information: AuthenticationScheme: Cookies was forbidden.
info: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[13]
      AuthenticationScheme: oidc was forbidden.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler: Information: AuthenticationScheme: oidc was forbidden.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAntiforgery(options =>
            {
                options.Cookie.HttpOnly = false;
                options.HeaderName = "XSRF-TOKEN";
            });
services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";

            })
.AddCookie("Cookies", options =>
                {
                    options.Cookie.HttpOnly = true;
                    options.Cookie.Name = "app";
                    options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
                    options.SlidingExpiration = true;           

                })
.AddOpenIdConnect("oidc", options =>
                {

                    options.Authority = $"{OpenId["ServerUrl"]}";
                    options.RequireHttpsMetadata = false;
                    options.ClientId = OpenId["ClientId"];
                    options.ClientSecret = OpenId["ClientSecret"];
                    options.ResponseType = "code";
                    options.UsePkce = true;
                    options.SignInScheme = "Cookies";

                    options.CallbackPath = "/signin-callback";
                    options.GetClaimsFromUserInfoEndpoint = true;
                    options.Scope.Clear();
                    options.Scope.Add("openid");
                    options.Scope.Add("offline_access");
                    options.Scope.Add("profile");
                    options.Scope.Add("role");
                    options.Scope.Add("myapp");
                    options.ClaimActions.MapJsonKey("website", "website");
                    options.ClaimActions.MapJsonKey(UserClaimTypes.Role, UserClaimTypes.Role);
                    options.ClaimActions.MapJsonKey(UserClaimTypes.UserId, UserClaimTypes.UserId);
                    options.SaveTokens = true;

                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        NameClaimType = "name",
                        RoleClaimType = UserClaimTypes.Role
                    };
                    options.Events = new OpenIdConnectEvents
                    {
                        OnTicketReceived = (e) =>
                       {
                           e.Properties.IsPersistent = true;
                           e.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60);

                           return Task.CompletedTask;
                       }

                    };

                });
services.AddSingleton<IAuthorizationPolicyProvider, AuthPolicyProvider>();
            services.AddSingleton<IAuthorizationHandler, ArticlePermissionHandler>();

            services.AddControllersWithViews(o =>
            {
                o.Filters.Add(typeof(ModelStateValidator));
                o.InputFormatters.Insert(0, GetJsonPatchInputFormatter());
            })
                .AddNewtonsoftJson();

}

public void Configure(
            IApplicationBuilder app,
            IWebHostEnvironment env,
            IDistributedCache cache,
            IHttpContextAccessor httpContextAccessor,
            ILogger<Startup> logger,
            IAntiforgery antiforgery)
        {
 app.UseStaticFiles();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

 app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapControllers();
            });

ArticlePermissionRequirement.cs

using System;
using Microsoft.AspNetCore.Authorization;

namespace App.Application.Services.Authorization.Policies.Requirements
{
    public class ArticlePermissionRequirement : IAuthorizationRequirement
    {
        public string PermissionName { get; }

        public ArticlePermissionRequirement(string permissionName)
        {
            PermissionName = permissionName ?? throw new ArgumentNullException(nameof(permissionName));
        }
    }
}

AuthPolicyProvider.cs

using System.Threading.Tasks;
using App.Application.Services.Authorization.Operations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;

namespace App.Application.Services.Authorization.Policies.Providers
{
    public class AuthPolicyProvider : DefaultAuthorizationPolicyProvider
    {
        public AuthPolicyProvider(IOptions<AuthorizationOptions> options) : base(options)
        {
        }


        public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
        {
            var policy = await base.GetPolicyAsync(policyName);

            if (policy == null)
            {
                policy = new AuthorizationPolicyBuilder()
                .AddRequirements(RequirementOperations.Requirement(policyName))
                .Build();
            }

            return policy;
        }
    }
}

ArticlePermissionHandler.cs



namespace App.Application.Services.Authorization.Policies.Handlers
{
    public class ArticlePermissionHandler : AuthorizationHandler<ArticlePermissionRequirement, Owner>
    {
        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            ArticlePermissionRequirement requirement,
            Owner resource)
        {

           // for test purpose (always true) I putted this
            context.Succeed(requirement);

            var user = context.User;
           
            if (user == null)
            {
               
                return Task.CompletedTask;
            }


            var roleClaim = user.Claims.FirstOrDefault(
                c => c.Type == ClaimTypes.Role
                )?.Value;


            if (roleClaim == null)
            {
               
                return Task.CompletedTask;
            }

            // Administrator and Manager can do all things
            if (user.IsInRole(Roles.Administrator) || user.IsInRole(Roles.Manager))
            {
               
                context.Succeed(requirement);
            }
            // Moderator can read, create, update all articles
            else if (Equals(requirement.PermissionName, Permissions.ReadArticle)
                || Equals(requirement.PermissionName, Permissions.CreateArticle)
                || Equals(requirement.PermissionName, Permissions.UpdateArticle))
            {
                if (user.IsInRole(Roles.Moderator))
                {
                    context.Succeed(requirement);
                }
            }
            // Writer can create new article
            else if (Equals(requirement.PermissionName, Permissions.CreateArticle))
            {
                if (user.IsInRole(Roles.Writer))
                {
                    context.Succeed(requirement);
                }
            }
            // Onwer can read, update, delete self article
            else if (Equals(requirement.PermissionName, Permissions.ReadArticle)
                || Equals(requirement.PermissionName, Permissions.UpdateArticle)
                || Equals(requirement.PermissionName, Permissions.DeleteArticle))
            {
                ClaimsPrincipal claimUser = context.User;
                var userClaimExtractor = new UserClaimExtractor(claimUser);
                var userId = userClaimExtractor.GetId();

                if (resource.OwnerId == userId)
                {
                    context.Succeed(requirement);
                }
            }
          
            return Task.CompletedTask;
        }
    }
}

HasPermissionAttribute.cs

using Microsoft.AspNetCore.Authorization;

namespace App.Application.Services.Authorization.Policies.Attributes
{
    public class HasPermissionAttribute : AuthorizeAttribute
    {
        public HasPermissionAttribute(string permissionName)
        {
            PermissionName = permissionName;
        }

        public string PermissionName
        {
            get
            {
                return Policy;
            }
            set
            {
                Policy = value;
            }
        }

    }
}

控制器

 public class DraftController : ApiController
    {
        public DraftController(IMediator mediator,
            IHttpContextAccessor context) : base(mediator, context)
        {
        }

 [HasPermission(Permissions.CreateArticle)]
        [HttpPost("drafts")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Drafts([FromBody] JsonPatchDocument<DraftDto> patchDoc)
        {
         ...
         }

但是有一件有趣的事情 - 当我用授权重写属性时:

        [Authorize]
        [HttpPost("drafts")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Drafts(

然后应用程序被授权,我可以使用动作草稿。但是使用 HasPermission 属性授权总是失败。

在哪里可以找到问题?为什么日志写:“Cookie 已成功验证”然后是“Cookie 被禁止”?

是的!我有!问题出在资源所有者身上。

AuthorizationHandler<ArticlePermissionRequirement, Owner>

删除此资源后,ArticlePermissionHandler 开始工作。

AuthorizationHandler<ArticlePermissionRequirement>

有助于理解问题。