Может ли ASP.Net MVC 4 OAuthWebSecurity открыть всплывающее окно

Я пытаюсь выяснить, как использовать новую функцию OAuthWebSecurity для ASP.Net MVC 4. Возможно ли, когда вы нажмете на внешнюю кнопку входа в facebook или twitter, чтобы сообщение формы было всплывающим, а не обновляло текущую страницу? Я использовал oauth с Twitter и Facebook, прежде чем использовать Javascript, и внешняя аутентификация произойдет во всплывающем окне. После того, как результаты будут возвращены асинхронно, всплывающее окно закроется. Могу ли я сделать что-то подобное этому, используя новую функциональность OAuthWebSecurity от MVC 4? Спасибо.

Ответ 1

Существует несколько аспектов решения этой проблемы:

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

Вот как я выполнил эти требования, используя шаблон MVC4 Internet Application в качестве отправной точки:

Чтобы запустить последовательность аутентификации во всплывающем окне (вместо перенаправления на новую страницу), вам нужно изменить _ExternalLoginListPartial.cshtml, чтобы обратная передача его формы была направлена ​​на всплывающее окно, запускаемое функцией JavaScript:

@model ICollection<AuthenticationClientData>

@if (Model.Count == 0)
{
    <div class="message-info">
        <p>There are no external authentication services configured. See <a href="#" onclick="location.href='http://go.microsoft.com/fwlink/?LinkId=252166'; return false;">this article</a>
        for details on setting up this ASP.NET application to support logging in via external services.</p>
    </div>
}
else
{
    <form id="login-launch" action="@Url.Action("ExternalLogin", "Account")" method="POST" target="login-popup" onsubmit="invokeLogin();">
        @Html.AntiForgeryToken()
        <fieldset id="socialLoginList">
            <input type="hidden" id="provider" name="provider" />
            <input type="hidden" name="returnUrl" value="@ViewBag.ReturnUrl"/>
            <p>
                @foreach (var p in OAuthWebSecurity.RegisteredClientData)
                {
                    <button type="submit" onclick="$('#provider').attr('value', '@p.DisplayName'); $('#login-launch').submit();" title="Log in using @p.DisplayName">@p.DisplayName</button>
                }
            </p>
        </fieldset>
    </form>
}

<script type="text/javascript">
    function invokeLogin() {
        var chrome = 100;
        var width = 500;
        var height = 500;
        var left = (screen.width - width) / 2;
        var top = (screen.height - height - chrome) / 2;
        var options = "status=0,toolbar=0,location=1,resizable=1,scrollbars=1,left=" + left + ",top=" + top + ",width=" + width + ",height=" + height;
        window.open("about:blank", "login-popup", options);
    }
</script>

В текущем состоянии этот код правильно запускает всплывающее окно и позволяет выполнить последовательность аутентификации, но всплывающее окно остается открытым и, если был указан URL-адрес перенаправления, всплывающее окно отображает эту страницу вместо перенаправления родительской страницы на этот URL-адрес.

Чтобы всплывающее окно закрылось после успешной (или неудачной) аутентификации, влечет за собой изменение метода действия контроллера, который обрабатывает обратный вызов аутентификации, так что он возвращает пользовательский вид, содержащий JavaScript, который отклоняет всплывающее окно. Как мы увидим ниже, этот механизм также может быть использован для реализации решений задач [3] и [4] выше.

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
    var result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

    if (!result.IsSuccessful)
    {
        return View("LoginResult", new LoginResultViewModel(false, Url.Action("ExternalLoginFailure")));
    }

    if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
    {
        return View("LoginResult", new LoginResultViewModel(true, returnUrl));
    }

    OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, result.UserName);
    return View("LoginResult", new LoginResultViewModel(true, returnUrl));
}

Этот метод действий является упрощенной версией метода ExternalLoginCallback(), который поставляется с исходным шаблоном проекта. В отличие от первоначальной реализации этот упрощенный пример не позволяет пользователю определять персонализированное имя пользователя при создании новой учетной записи и не позволяет связать несколько учетных записей OAuth или OpenID с одной учетной записью пользователя MVC. Тем не менее, эти возможности возможны, расширяя приведенный выше шаблон, чтобы включить более сложную логику исходного шаблона.

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

Вот разметка для представления LoginResult:

@model LoginResultViewModel
@{
    Layout = null;

    var success = Model.Success ? "true" : "false";
    var returnUrl = Model.ReturnUrl == null ? "null" : string.Format("'{0}'", Model.ReturnUrl);
}

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        if (window.opener && window.opener.loginCallback) {
            window.opener.loginCallback(@success, @Html.Raw(returnUrl));
        }

        window.close();
    </script>
</head>
</html>

В приведенном выше представлении допускается модель типа LoginResultViewModel, которая отражает результат завершенной попытки аутентификации:

public class LoginResultViewModel
{
    public LoginResultViewModel(bool success, string returnUrl)
    {
        Success = success;
        ReturnUrl = returnUrl;
    }

    public bool Success { get; set; }
    public string ReturnUrl { get; set; }
}

При использовании всех вышеперечисленных элементов можно запустить последовательность аутентификации, которая выполняется во всплывающем окне, которое автоматически закрывается, когда последовательность завершается. Если аутентификация прошла успешно, пользователь будет входить в систему в этот момент, и если он был запущен с URL-адресом возврата (как это происходит автоматически, если это вызвано запросом метода действия, защищенного атрибутом [Authorize]), родительская страница будет перенаправлен на первоначально запрошенный URL.

Однако, если аутентификация была явно запущена пользователем (например, посетив страницу входа в систему), родительская страница не будет перенаправляться и поэтому может потребовать обновления с частичной страницей, чтобы отразить тот факт, что пользователь теперь зарегистрирован, В образце шаблона MVC необходимо обновить страницу, чтобы отобразить имя пользователя и кнопку Logout вместо кнопок Login и Register.

Это может быть достигнуто путем определения функции обратного вызова JavaScript в представлении макета, который вызывается JavaScript, выполняемым всплывающим окном аутентификации:

<script type="text/javascript">
    function loginCallback(success, returnUrl) {
        if (returnUrl) {
            window.location.href = returnUrl;
        } else {
            $.ajax({
                url: '@Url.Action("LoginPartial", "Account")',
                success: function (result) {
                    $('#login').html(result);
                }
            });
        }
    }
</script>

Вышеупомянутый JavaScript делает вызов AJAX новым методом действий, который отображает и возвращает существующий вид _LoginPartial:

[HttpGet]
public ActionResult LoginPartial()
{
    if (Request.IsAjaxRequest())
    {
        return View("_LoginPartial");
    }

    return new EmptyResult();
}

Для первоначального шаблона проекта требуется последняя модификация. Представление _LoginPartial должно быть изменено для рендеринга без вида макета:

@{
    Layout = null;
}