Asp .Net Core Почему вызывается мой обработчик авторизации, даже если пользователь не прошел аутентификацию? - PullRequest
1 голос
/ 28 июня 2019

У меня проблема, если я вручную перехожу на страницу после входа в систему, я получаю исключение в одном из дескрипторов моей политики, поскольку он не может найти претензию.

Почему это происходит вместо того, чтобы перенаправить пользователя на страницу входа, потому что он не аутентифицирован?Я даже установил схему аутентификации, потому что все мои страницы имеют

[Authorize("scheme"), Policy = "policy"]

Вот весь мой код запуска

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication()
         .AddCookie("ProductionAuth", options =>
         {
             options.ExpireTimeSpan = TimeSpan.FromDays(1);
             options.LoginPath = new PathString("/Production/Index");
             options.LogoutPath = new PathString("/Production/Logout");
             options.AccessDeniedPath = new PathString("/Production/AccessDenied/");
             options.SlidingExpiration = true;
         })
        .AddCookie("AdministrationAuth", options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromDays(1);
            options.LoginPath = new PathString("/Administration/Index");
            options.LogoutPath = new PathString("/Administration/Logout");
            options.AccessDeniedPath = new PathString("/Administration/AccessDenied/");
            options.SlidingExpiration = true;
        });

        services.AddAuthorization(options =>
        {
            options.AddPolicy("HasArranqueActivo", policy =>
                policy.RequireAuthenticatedUser()
                .AddAuthenticationSchemes("ProductionAuth")
                .Requirements.Add(new HasArranqueActivoRequirement()
            ));

            options.AddPolicy("HasArranqueInactivo", policy =>
                 policy.RequireAuthenticatedUser()
                 .AddAuthenticationSchemes("ProductionAuth")
                .Requirements.Add(new HasArranqueInactivoRequirement()
            ));
                 options.AddPolicy("IsParagemNotOnGoing", policy =>
                 policy.RequireAuthenticatedUser()
                 .AddAuthenticationSchemes("ProductionAuth")
                 .Requirements.Add(new IsParagemNotOnGoingRequirement()
            ));
                 options.AddPolicy("IsParagemOnGoing", policy =>
                 policy.RequireAuthenticatedUser()
                 .AddAuthenticationSchemes("ProductionAuth")
                 .Requirements.Add(new IsParagemOnGoingRequirement()
            ));
        });

        services.AddMemoryCache();

        services.AddMvc()
         .AddRazorPagesOptions(options =>
         {
             options.AllowAreas = true;
             options.Conventions.AuthorizeAreaFolder("Administration", "/Account");
             options.Conventions.AuthorizeAreaFolder("Production", "/Account");
         })
         .AddNToastNotifyToastr(new ToastrOptions()
         {
             ProgressBar = true,
             TimeOut = 3000,
             PositionClass = ToastPositions.TopFullWidth,
             PreventDuplicates = true,
             TapToDismiss = true
         })
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);


    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseNToastNotify();
        app.UseAuthentication();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

Это мой обработчик, каждый обработчик в основном одинаков.Требования являются пустыми, они не имеют ничего в теле.

  public class IsParagemNotOnGoingHandler : AuthorizationHandler<IsParagemNotOnGoingRequirement>
    {
        private readonly DatabaseContext _context;

        public IsParagemNotOnGoingHandler(DatabaseContext context)
        {
            _context = context;
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsParagemNotOnGoingRequirement requirement)
        {
            // Get the context       
            var redirectContext = context.Resource as AuthorizationFilterContext;

            var registoId = Convert.ToInt32(context.User.FindFirst(c => c.Type == ClaimTypes.PrimarySid).Value);
            bool IsRegistoOnGoing = _context.ParagensRegistos.Any(pr => pr.RegistoId == registoId && pr.HoraFim == null);

            if (!IsRegistoOnGoing)
            {
                context.Succeed(requirement);
            }
            else
            {
                redirectContext.Result = new RedirectToPageResult("/Paragem");
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }

, и это исключение

Произошло необработанное исключение при обработке запроса.NullReferenceException: ссылка на объект не установлена ​​на экземпляр объекта.NoPaper.Policies.NoPaper.Policies.Microsoft.AspNetCore.Authorization.AuthorizationHandler.HandleAsync (контекст AuthorizationHandlerContext) Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.AuthorizeAsync (пользователь ClaimsPrincipal, ресурс объекта, требования IEnumerateAut ЦенаКонтекст HttpContext, ресурс объекта). Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter.OnAuthorizationAsync (контекст AuthorizationFilterContext).Routing.EndpointMiddleware.Invoke (HttpContext httpContext)контекст ttpContext) NToastNotify.NtoastNotifyAjaxToastsMiddleware.InvokeAsync (контекст HttpContext, RequestDelegate далее) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions + <> c__DisplayClass5_1 +d.MoveNext () Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke (контекст HttpContext) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke (контекст HttpContext)

Ответы [ 2 ]

2 голосов
/ 28 июня 2019

В документах есть важное примечание, в котором говорится:

Обработчики авторизации вызываются даже в случае сбоя аутентификации.

В вашем случае аутентификация не удалась, но ваш IsParagemNotOnGoingHandler HandleRequirementAsync все еще вызывается. Чтобы решить проблему, вы можете просто сделать реализацию вашего обработчика более устойчивой к отсутствию заявки. Вот пример для полноты:

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IsParagemNotOnGoingRequirement requirement)
{
    if (!context.User.HasClaim(c => c.Type == ClaimTypes.PrimarySid))
        return Task.CompletedTask;

    ...
}

Возможно, вы захотите защитить и от сбоя Convert.ToInt32, если значение претензии не преобразуется в int.

1 голос
/ 28 июня 2019

Я вижу несколько случаев, когда непонятные исключения могут вызвать у вас головную боль. Их можно предотвратить следующим образом.

  • Оператор as выполняет безопасное приведение, которое возвращает нулевое значение при неверном приведении. Ваша попытка доступа члена на нем redirectContext.Result может скинуть. Вы можете использовать присвоение соответствия шаблону C # 7.0, чтобы выполнить безопасное приведение с проверкой достоверности в одном операторе. Если вы не можете использовать C # 7, добавьте простую нулевую проверку перед доступом.
if (context.Resource is AuthorizationFilterContext filterContext)
  • Пользователь не может иметь претензию. В дополнение к тому, что упомянуто в комментариях, пользователь может иметь или не иметь претензии, даже если они аутентифицированы. Мой предпочтительный шаблон доступа для этого - нулевой условный доступ с первым или заданным по умолчанию linq.
var primarySid = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.PrimarySid)?.Value
  • Заявка PrimarySid может быть или не быть int. Вы можете не устанавливать строку в качестве PrimarySid специально, но могут возникать ошибки или злоумышленник может.
if (!int.TryParse(primarySid, out var registoId))
  • Необязательно, поскольку метод возвращает задание, вы можете выполнить асинхронный поиск в базе данных, который лучше масштабируется и имеет дополнительное преимущество: нет необходимости возвращать завершенные задачи каждый раз, когда вы возвращаетесь.

Соединяя все это,

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IsParagemNotOnGoingRequirement requirement)
{
    var user = context.User;
    if (!user.Identity.IsAuthenticated)
    {
        return;
    }

    var primarySid = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.PrimarySid)?.Value
    if (string.IsNullorWhiteSpace(primarySid)) 
    {
        return;
    }

    int registoId;
    if (!int.TryParse(rawUserId, out registoId)) 
    {
        return;
    }

    bool IsRegistoOnGoing = await _context.ParagensRegistos
                  .AnyAsync(pr => pr.RegistoId == registoId && pr.HoraFim == null);

    if (!IsRegistoOnGoing)
    {
        context.Succeed(requirement); // success!!
    }
    else
    {
        if (context.Resource is AuthorizationFilterContext filterContext)
        {
             filterContext.Result = new RedirectToPageResult("/Paragem");
        }     
        context.Succeed(requirement); // success here again?
    }
}

Кроме того, ваше требование выполняется независимо от того, является ли условие истинным или ложным, и вы выполнили побочный эффект в вашем обработчике авторизации, который должен вызвать перенаправление. Это не так, как это должно работать. Вы должны выполнить или выполнить требование, которое, в свою очередь, приводит к перенаправлению, которое вы настроили в другом месте. Если ваши условия ничего не значат, а вы смешиваете проблемы и нарушаете принцип разделения интересов , ваше приложение будет сложно обслуживать.

Если вы придерживаетесь этой архитектуры, вы можете сократить финал, если до,

if (IsRegistoOnGoing && context.Resource is AuthorizationFilterContext filterContext)
{
    filterContext.Result = new RedirectToPageResult("/Paragem");
}
context.Succeed(requirement);

Я настоятельно рекомендую пересмотреть код, как только вы исправите возможные исключения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...