我试图弄清楚如何使用 ASP.Net MVC 4 的新 OAuthWebSecurity 功能。单击 facebook 或 twitter 外部登录按钮时是否可以将表单发布到弹出窗口而不是刷新当前页面?在使用 Javascript 之前,我在 Twitter 和 Facebook 中使用了 oauth,外部身份验证将在弹出窗口中进行。异步返回结果后,弹出窗口将关闭。我可以使用 MVC 4 的新 OAuthWebSecurity 功能做类似的事情吗?谢谢。
1 回答
解决这个问题有几个方面:
- 打开一个弹出窗口以适应身份验证序列。
- 验证完成后关闭弹出窗口。
- 处理身份验证失败。
- 更新父页面以反映用户已通过身份验证的事实。
以下是我实现这些要求的方式,使用 MVC4Internet 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>
在当前状态下,此代码正确启动弹出窗口并允许执行身份验证序列,但弹出窗口保持打开状态,如果指定了重定向 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 对呈现并返回现有_LoginPartial
视图的新操作方法进行 AJAX 调用:
[HttpGet]
public ActionResult LoginPartial()
{
if (Request.IsAjaxRequest())
{
return View("_LoginPartial");
}
return new EmptyResult();
}
需要对原始项目模板进行最后一次修改。_LoginPartial
必须修改视图以在没有布局视图的情况下呈现:
@{
Layout = null;
}