Одна страница входа для нескольких типов аутентификации (включая Azure AD) в .NET Core MVC - PullRequest
0 голосов
/ 25 октября 2019

У меня есть .NET Core 2.2 MVC веб-приложение. И я добавил туда два типа аутентификации / провайдеров:

  1. Логин / пароль с базой данных локальных пользователей (пользовательская вещь, без .NET Core Identity )
  2. Azure AD

Моя цель - создать страницу входа в систему /account/login, где пользователи могут выбирать между этими двумя аутентификациями и входить в систему с помощью любой из них. Таким образом, каждый раз, когда неаутентифицированный пользователь открывает любую страницу (с контроллера с [Authorize] attrubite), он перенаправляется на страницу /account/login, которая имеет веб-форму логина / пароля с собственной кнопкой отправки и, кроме того, Office 365 login ссылка / кнопка.

Просто чтобы прояснить ситуацию - мне не нужна пользовательская страница входа Microsoft / Azure AD. Я только хочу, чтобы неаутентифицированные пользователи сначала получали мою страницу входа, откуда они могут либо войти в систему, используя мою веб-форму, либо щелкнуть Office 365 login и перейти на страницу входа Microsoft.

Теперь проверка подлинностичасть выполнена и, кажется, работает нормально, я могу войти с любой из аутентификаций, но мой план с перенаправлением неаутентифицированного пользователя на /account/login не удался. Вместо этого пользователь сразу же перенаправляется на страницу входа Microsoft. Таким образом, похоже, что аутентификация Azure AD имеет более высокий приоритет.

Вот моя реализация.

Startup.cs:

// ...

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    // the presence of CookieAuthenticationDefaults.AuthenticationScheme doesn't seem to influence anything
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    // makes no difference either
    //services.AddAuthentication(
    //    options =>
    //    {
    //        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    //        options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    //        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    //    }
    //)
        .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, // and it also can be omitted here
            options =>
            {
                options.LoginPath = "/Account/Login";
                options.ExpireTimeSpan = TimeSpan.FromDays(45);
            })
        .AddAzureAD(options => _configuration.Bind("AzureAD", options));

    services.AddAuthorization(options =>
    {
        // as the default policy, it applies to all [Authorize] controllers
        options.DefaultPolicy = new AuthorizationPolicyBuilder(
            CookieAuthenticationDefaults.AuthenticationScheme,
            AzureADDefaults.AuthenticationScheme
        )
        .RequireAuthenticatedUser() // a simple policy that only requires a user to be authenticated
        .Build();
    });

    services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
    {
        options.Authority = options.Authority + "/v2.0/";
        options.TokenValidationParameters.ValidateIssuer = false;
    });

    services.AddMvc(options =>
    {
        // it is my understanding that there is no need create a policy here
        // and perform "options.Filters.Add(new AuthorizeFilter(policy))",
        // because the default policy is already added and controllers have explicit [Authorize] attribute
        // [...] well, actually I tried that too, but it didn't change anything

        options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
    })
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

public void Configure(
    IApplicationBuilder app,
    IHostingEnvironment env,
    ILoggerFactory loggerFactory
    )
{
    // ...

    app.UseCookiePolicy();
    app.UseAuthentication();

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

AccountController.cs:

[Authorize]
[Route("account")]
public class AccountController : Controller
{
    // ...

    // that is where "Office 365 login" link leads
    [HttpGet("login-ad")]
    [AllowAnonymous]
    public IActionResult LoginAD(string returnUrl = null)
    {
        if (User.Identity.IsAuthenticated)
        {
            return RedirectToAction("Index", "Account");
        }
        else
        {
            if (string.IsNullOrEmpty(returnUrl)) { returnUrl = "/"; }
            return Challenge(
                new AuthenticationProperties { RedirectUri = returnUrl },
                AzureADDefaults.AuthenticationScheme
                );
        }
    }

    [HttpGet("login")]
    [AllowAnonymous]
    public IActionResult Login(string returnUrl = null)
    {
        if (User.Identity.IsAuthenticated)
        {
            return RedirectToAction("Index", "Account");
        }

        ViewData["ReturnUrl"] = returnUrl;
        return View();
    }

    // that is where login/password web-form submits to
    [HttpPost("login")]
    [AllowAnonymous]
    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
        if (ModelState.IsValid)
        {
            await _usersManager.SignIn(
                model.Login,
                model.Password
                );

            // ...

            return LocalRedirect(returnUrl);
        }

        ViewData["ReturnUrl"] = returnUrl;
        return View(model);
    }

    // ...
}

HomeController.cs:

[Authorize]
public class HomeController : Controller
{
    // ...

    public IActionResult Index()
    {
        return View();
    }

    // ...
}

Итак, открытие любой страницы неаутентифицированным пользователем приводит к немедленному перенаправлению на страницу входа Microsoft. А чтобы получить /account/login (чтобы иметь возможность войти в систему с использованием другой аутентификации), пользователям необходимо явно открыть этот URL-адрес.

Если я удаляю AzureADDefaults.AuthenticationScheme из политики по умолчанию, то все неаутентифицированные запросытеперь будет перенаправлено на /account/login - именно то, что я хочу - но, естественно, аутентификация Azure AD больше не работает:

Эти перенаправления говорят мне об этом после успешной аутентификации в MicrosoftСтраница входа возвращает пользователя на /account/login, но пользователь все еще не аутентифицирован на моем веб-сайте.

Я, конечно, могу добавить [AllowAnonymous] к Index действию HomeController и вернуть перенаправление на/account/login для неаутентифицированных пользователей, но это, очевидно, будет работать только для / маршрута.

У меня такое чувство, что я не понимаю некоторые вещи о AddAuthentication(), схемах и политиках, таким образом, очевидно, я сделалчто-то не так в Startup.cs. Можете ли вы помочь мне понять, что там не так? Или, может быть, есть какой-то другой способ добиться того, чего я хочу?

1 Ответ

2 голосов
/ 25 октября 2019

Обновленный ответ

Я решил клонировать пример проекта, упомянутый здесь в документации по quickstart-v2-aspnet-core-webapp, и посмотреть, смогу ли я воспроизвести вашу ошибку.

После клонирования проекта я добавил два пакета NuGet.

  • Microsoft.AspNetCore.Identity 2.2.0
  • Microsoft.AspNetCore.Identity.EntityFrameworkCore 2.2.0

Затем добавлен контекст базы данных, который расширяет IdentityContext.

  • ApplicationDbContext.cs

In Startup.cs

  • Зарегистрированный идентификатор
  • Зарегистрированный контекст базы данных и предоставленная строка подключения

В AppSettings.json

  • Настроены TenantID и ClientID

Ранприложение.

В этот момент приложение запускается и перенаправляет меня в Учетную запись / Вход в систему, где я выбираю Вход через учетную запись Microsoft.

Теперь я, очевидно, вижу, что что-то не так. Он не будет аутентифицировать пользователя.

Получается:

Метод расширения .AddAzureAd() фактически нельзя использовать в сочетании с другими методами аутентификации. См. эту проблему на github.

Но, к счастью, обходной путь довольно прост. Просто отключите .AddAzureAd() для .AddOpenIdConnect() и измените раздел AzureAd в AppSettings на:

  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Authority": "https://login.microsoftonline.com/{tenantID}/v2.0/",
    "TenantId": "{tenantID}",
    "ClientId": "{clientID}",
    "CallbackPath": "/signin-oidc"
  },

Теперь я могу отлично входить в систему с учетными записями AzureAD и локальных пользователей.

Для вашего удобства я загрузил полный пример проекта на свою страницу GitHub .

...