Как принудительно выполнить повторную проверку подлинности между веб-приложением ASP Net Core 2.0 MVC и Azure AD

У меня есть веб-приложение ASP.Net Core MVC, которое использует Azure AD для проверки подлинности. Я только что получил новое требование, чтобы вынудить пользователя выполнить повторную аутентификацию перед вводом некоторой конфиденциальной информации (кнопка для ввода этой новой информации вызывает действие контроллера, которое инициализирует новую модель представления и возвращает частичное представление в модал начальной загрузки).

Я следовал этой статье, которая является отличным руководством для достижения именно этого требования. Мне пришлось внести некоторые изменения, чтобы заставить его работать с ASP.Net Core 2.0, что я считаю правильным, однако мои проблемы заключаются в следующем...

  1. Добавление декорации фильтра ресурсов "[RequireReauthentication(0)]" к моему действию контроллера работает, однако передача значения 0 означает, что код никогда не достигает команды await.next() внутри фильтра. Если я изменяю значение параметра на 30, это работает, но кажется очень произвольным. Каким должно быть это значение?

  2. Повторная аутентификация работает при вызове действия контроллера, которое возвращает полный просмотр. Однако, когда я вызываю действие из ajax-запроса, который возвращает частичное в загрузочный модал, он завершается неудачей, прежде чем загружать модальный с

Ответ на запрос предварительной проверки не проходит проверку контроля доступа: в запрошенном ресурсе отсутствует заголовок "Access-Control-Allow-Origin". Происхождение ' https://localhost:44308/', следовательно, доступ запрещен

Это похоже на проблему CORS, но я не знаю, почему это будет работать при прохождении стандартного процесса mvc, а не при вызове из jquery. Добавление

services.AddCors ();

app.UseCors (builder => builder.WithOrigins (" https://login.microsoftonline.com/"));

к моему файлу запуска не имеет никакого значения. В чем может быть проблема здесь?

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // Ommitted for clarity...

    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddAzureAd(options => Configuration.Bind("AzureAd", options))
    .AddCookie();

    services.AddCors();

    // Ommitted for clarity...
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // Ommitted for clarity...

    app.UseCors(builder => builder.WithOrigins("https://login.microsoftonline.com"));

    app.UseStaticFiles();

    app.UseAuthentication();

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

AzureAdAuthenticationBuilderExtensions.cs

public static class AzureAdAuthenticationBuilderExtensions
{        
    public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
        => builder.AddAzureAd(_ => { });

    public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
    {
        builder.Services.Configure(configureOptions);
        builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
        builder.AddOpenIdConnect(options =>
        {
            options.ClaimActions.Remove("auth_time");
            options.Events = new OpenIdConnectEvents
            {
                OnRedirectToIdentityProvider = RedirectToIdentityProvider
            };
        });
        return builder;
    }

    private static Task RedirectToIdentityProvider(RedirectContext context)
    {
        // Force reauthentication for sensitive data if required
        if (context.ShouldReauthenticate())
        {
            context.ProtocolMessage.MaxAge = "0"; // <time since last authentication or 0>;
        }
        else
        {
            context.Properties.RedirectUri = new PathString("/Account/SignedIn");
        }

        return Task.FromResult(0);
    }

    internal static bool ShouldReauthenticate(this RedirectContext context)
    {
        context.Properties.Items.TryGetValue("reauthenticate", out string reauthenticate);
        bool shouldReauthenticate = false;

        if (reauthenticate != null && !bool.TryParse(reauthenticate, out shouldReauthenticate))
        {
            throw new InvalidOperationException($"'{reauthenticate}' is an invalid boolean value");
        }

        return shouldReauthenticate;
    }

    // Ommitted for clarity...
}

RequireReauthenticationAttribute.cs

public class RequireReauthenticationAttribute : Attribute, IAsyncResourceFilter
{
    private int _timeElapsedSinceLast;
    public RequireReauthenticationAttribute(int timeElapsedSinceLast)
    {
        _timeElapsedSinceLast = timeElapsedSinceLast;
    }
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        var foundAuthTime = int.TryParse(context.HttpContext.User.FindFirst("auth_time")?.Value, out int authTime);
        var ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds();

        if (foundAuthTime && ts - authTime < _timeElapsedSinceLast)
        {
            await next();
        }
        else
        {
            var state = new Dictionary<string, string> { { "reauthenticate", "true" } };
            await AuthenticationHttpContextExtensions.ChallengeAsync(context.HttpContext, OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties(state));
        }
    }
}

CreateNote.cs

[HttpGet]
[RequireReauthentication(0)]
public IActionResult CreateNote(int id)
{
    TempData["IsCreate"] = true;
    ViewData["PostAction"] = "CreateNote";
    ViewData["PostRouteId"] = id;
    var model = new NoteViewModel
    {
        ClientId = id
    };
    return PartialView("_Note", model);
}

Razor View (фрагмент)

<a asp-controller="Client" asp-action="CreateNote" asp-route-id="@ViewData["ClientId"]" id="client-note-get" data-ajax="true" data-ajax-method="get" data-ajax-update="#client-note-modal-content" data-ajax-mode="replace" data-ajax-success="ShowModal('#client-note-modal', null, null);" data-ajax-failure="AjaxFailure(xhr, status, error, false);"></a>

Вся помощь приветствуется. Спасибо

1 ответ

Проблема с CORS отсутствует в вашем приложении. Ваш AJAX-вызов пытается выполнить перенаправление аутентификации в Azure AD, что не будет работать.

Что вы можете сделать вместо этого в вашем RedirectToIdentityProvider функция, проверьте, является ли запрос запросом AJAX. Если это так, верните код состояния 401, без перенаправления.

Затем JS на стороне клиента должен определить код состояния и выполнить перенаправление, которое запускает аутентификацию.