ASP.Net MVC 4的OAuthWebSecurity可以打开弹出窗口

Tom*_*eck 9 oauth-provider oauth-2.0 facebook-oauth asp.net-mvc-3 asp.net-mvc-4

我正在试图弄清楚如何使用ASP.Net MVC 4的新OAuthWebSecurity功能.点击Facebook或Twitter外部登录按钮是否有可能将表单发布到弹出窗口而不是刷新当前页面?在使用Javascript之前我曾在Twitter和Facebook上使用oauth,并且弹出窗口中会发生外部身份验证.异步返回结果后,弹出窗口将关闭.我可以使用MVC 4的新OAuthWebSecurity功能执行类似的操作吗?谢谢.

Tim*_*ter 28

解决这个问题有几个方面:

  1. 打开弹出窗口以容纳身份验证序列.
  2. 验证完成后关闭弹出窗口.
  3. 处理身份验证失败
  4. 更新父页面以反映用户已通过身份验证的事实.

以下是我使用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="http://go.microsoft.com/fwlink/?LinkId=252166">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>
Run Code Online (Sandbox Code Playgroud)

在其当前状态下,此代码正确启动弹出窗口并允许执行身份验证序列,但弹出窗口仍保持打开状态,如果指定了重定向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));
}
Run Code Online (Sandbox Code Playgroud)

此操作方法是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>
Run Code Online (Sandbox Code Playgroud)

上面的视图接受LoginResultViewModel反映完成的身份验证尝试结果的类型模型:

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

    public bool Success { get; set; }
    public string ReturnUrl { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

有了上述所有元素,就可以启动一个在弹出窗口中执行的身份验证序列,该序列在序列完成时自动关闭.如果身份验证成功,则用户将在此时登录,如果它是使用返回URL启动的(如果由对[Authorize]属性保护的操作方法的请求触发,则会自动发生),将重定向父页面到最初请求的URL.

但是,如果用户明确启动了身份验证(例如,通过访问登录页面),则父页面将不会重定向,因此可能需要部分页面更新以反映用户现在已登录的事实.在MVC模板样本,它是需要更新的页面来显示用户的名称和一个Logout按钮,而不是LoginRegister按钮.

这可以通过在布局视图中定义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>
Run Code Online (Sandbox Code Playgroud)

上面的JavaScript对一个新的动作方法进行AJAX调用,该方法呈现并返回现有_LoginPartial视图:

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

    return new EmptyResult();
}
Run Code Online (Sandbox Code Playgroud)

原始项目模板需要进行最后一次修改.的_LoginPartial视图必须进行修改,以使没有一个布局图:

@{
    Layout = null;
}
Run Code Online (Sandbox Code Playgroud)