通过 MVC 4 使用 OAuth 提供程序

作者 Tom FitzMacken

本教程介绍如何生成 ASP.NET MVC 4 Web 应用程序,使用户能够使用来自外部提供程序(如 Facebook、Twitter、Microsoft 或 Google)的凭据登录,然后将这些提供程序的某些功能集成到 Web 应用程序中。 为简单起见,本教程重点介绍如何使用 Facebook 中的凭据。

若要在 ASP.NET MVC 5 Web 应用程序中使用外部凭据,请参阅 使用 Facebook 和 Google OAuth2 和 OpenID 登录创建 ASP.NET MVC 5 应用

在网站中启用这些凭据具有显著优势,因为数百万用户已经拥有这些外部提供商的帐户。 如果这些用户不必创建并记住一组新的凭据,则他们可能更倾向于注册您的网站。 此外,用户通过其中一个提供程序登录后,可以合并提供程序的社交操作。

生成目标

本教程中有两个main目标:

  1. 允许用户使用 OAuth 提供程序的凭据登录。
  2. 从提供程序检索帐户信息,并将该信息与站点的帐户注册集成。

尽管本教程中的示例侧重于使用 Facebook 作为身份验证提供程序,但你可以修改代码以使用任何提供程序。 实现任何提供程序的步骤与本教程中介绍的步骤非常相似。 仅当直接调用提供程序的 API 集时,才会注意到明显的差异。

先决条件

此外,本主题假定你已基本了解 ASP.NET MVC 和 Visual Studio。 如果需要 ASP.NET MVC 4 简介,请参阅 ASP.NET MVC 4 简介

创建项目

在 Visual Studio 中,创建新的 ASP.NET MVC 4 Web 应用程序,并将其命名为“OAuthMVC”。 可以面向 .NET Framework 4.5 或 4。

创建项目

在“新建 ASP.NET MVC 4 项目”窗口中,选择“ Internet 应用程序 ”,并将 Razor 保留为视图引擎。

选择“Internet 应用程序”

启用提供程序

使用 Internet 应用程序模板创建 MVC 4 Web 应用程序时,会在 App_Start 文件夹中使用名为 AuthConfig.cs 的文件创建项目。

AuthConfig 文件

AuthConfig 文件包含用于为外部身份验证提供程序注册客户端的代码。 默认情况下,此代码被注释掉,因此不会启用任何外部提供程序。

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        // To let users of this site log in using their accounts from other sites such as Microsoft, Facebook, and Twitter,
        // you must update this site. For more information visit https://go.microsoft.com/fwlink/?LinkID=252166

        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        //OAuthWebSecurity.RegisterFacebookClient(
        //    appId: "",
        //    appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();
    }
}

必须取消注释此代码才能使用外部身份验证客户端。 仅取消注释要包含在网站中的提供商。 在本教程中,仅启用 Facebook 凭据。

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        OAuthWebSecurity.RegisterFacebookClient(
            appId: "",
            appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();        
    }
}

请注意,在上面的示例中, 方法包含注册参数的空字符串。 如果现在尝试运行应用程序,应用程序将引发参数异常,因为不允许参数使用空字符串。 若要提供有效值,您必须向外部提供程序注册网站,如下一部分所示。

使用外部提供程序进行注册

若要使用来自外部提供程序的凭据对用户进行身份验证,您必须向提供程序注册网站。 注册站点时,将收到参数 (,例如密钥或 ID,以及注册客户端时要包括的机密) 。 必须拥有要使用的提供商的帐户。

本教程不提供使用这些提供程序进行注册所必须执行的全部步骤。 这些步骤通常比较简单。 若要成功注册您的网站,请按照这些网站上提供的说明操作。 若要开始注册您的网站,请查看下列门户的开发人员网站:

向 Facebook 注册网站时,可以为网站域和 "http://localhost/" URL 提供“localhost”,如下图所示。 使用 localhost 适用于大多数提供程序,但目前不适用于 Microsoft 提供程序。 对于 Microsoft 提供程序,必须包含有效的网站 URL。

注册站点

在上图中,已删除应用 ID、应用机密和联系人电子邮件的值。 实际注册网站时,这些值将存在。 需要记下应用 ID 和应用机密的值,因为需要将它们添加到应用程序。

创建测试用户

如果你不介意使用现有的 Facebook 帐户来测试网站,则可以跳过此部分。

可以在 Facebook 应用管理页中轻松为应用程序创建测试用户。 可以使用这些测试帐户登录到站点。 通过单击左侧导航窗格中的 “角色” 链接并单击“ 创建 ”链接来创建测试用户。

创建测试用户

Facebook 站点会自动创建你请求的测试帐户数。

从提供程序中添加应用程序 ID 和密码

现在,你已从 Facebook 收到 ID 和机密,请返回到 AuthConfig 文件,并将其添加为参数值。 下面所示的值不是实际值。

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        //OAuthWebSecurity.RegisterFacebookClient(
        //    appId: "",
        //    appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();
    }
}

使用外部凭据登录

这就是在站点中启用外部凭据所需的全部操作。 运行应用程序并单击右上角的登录链接。 该模板会自动识别你已将 Facebook 注册为提供商,并包含提供程序的按钮。 如果注册多个提供程序,则会自动包含每个提供程序的按钮。

外部登录

本教程不介绍如何自定义外部提供程序的登录按钮。 有关该信息,请参阅 使用 OAuth/OpenID 时自定义登录 UI

单击“Facebook”按钮,使用 Facebook 凭据登录。 选择其中一个外部提供程序时,系统会将你重定向到该站点,并且该服务会提示你登录。

下图显示了 Facebook 的登录屏幕。 它指出,你正在使用 Facebook 帐户登录到名为 oauthmvcexample 的站点。

facebook 身份验证

使用 Facebook 凭据登录后,页面会通知用户该网站将有权访问基本信息。

请求权限

选择“ 转到应用”后,用户必须注册站点。 下图显示了用户使用 Facebook 凭据登录后的注册页面。 用户名通常预填充提供程序的名称。

屏幕截图显示了“注册”页面,你可以在其中将 Facebook 帐户与此应用相关联。

单击“ 注册 ”以完成注册。 关闭浏览器。

可以看到新帐户已添加到数据库。 在服务器资源管理器中,打开 DefaultConnection 数据库并打开 Tables 文件夹。

数据库表

右键单击 “UserProfile” 表,然后选择“ 显示表数据”。

显示数据

你将看到你添加的新帐户。 查看 webpage_OAuthMembership 表中的数据。 你将看到更多与刚添加的帐户的外部提供程序相关的数据。

如果只想启用外部身份验证,则已完成操作。 但是,可以将提供程序中的信息进一步集成到新的用户注册过程中,如以下部分所示。

为其他用户信息创建模型

如前面部分所述,无需检索任何其他信息即可运行内置帐户注册。 但是,大多数外部提供程序会传回有关用户的其他信息。 以下部分演示如何保留该信息并将其保存到数据库。 具体而言,将保留用户全名、用户个人网页 URI 的值,以及指示 Facebook 是否已验证帐户的值。

你将使用Code First 迁移添加用于存储其他用户信息的表。 您将表添加到现有数据库,因此首先需要创建当前数据库的快照。 通过创建当前数据库的快照,稍后可以创建仅包含新表的迁移。 创建当前数据库的快照:

  1. 打开 包管理器控制台
  2. 运行 命令 enable-migrations
  3. 运行 add-migration initial –IgnoreChanges 命令
  4. 运行命令 update-database

现在,你将添加新属性。 在 Models 文件夹中,打开 AccountModels.cs 文件,并找到 RegisterExternalLoginModel 类。 RegisterExternalLoginModel 类保存从身份验证提供程序返回的值。 添加名为 FullName 和 Link 的属性,如下所示。

public class RegisterExternalLoginModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    public string ExternalLoginData { get; set; }

    [Display(Name = "Full name")]
    public string FullName { get; set; }

    [Display(Name = "Personal page link")]
    public string Link { get; set; }
}

此外,在 AccountModels.cs 中添加名为 ExtraUserInformation 的新类。 此类表示将在数据库中创建的新表。

[Table("ExtraUserInformation")]
public class ExternalUserInformation
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string FullName { get; set; }
    public string Link { get; set; }
    public bool? Verified { get; set; }
}

在 UsersContext 类中,添加下面突出显示的代码,为新类创建 DbSet 属性。

public class UsersContext : DbContext
{
    public UsersContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<ExternalUserInformation> ExternalUsers { get; set; }
}

现在可以创建新表了。 再次打开包管理器控制台,这次:

  1. 运行 命令 add-migration AddExtraUserInformation
  2. 运行命令 update-database

新表现在存在于数据库中。

检索其他数据

有两种方法可以检索其他用户数据。 第一种方法是在身份验证请求期间保留默认情况下传回的用户数据。 第二种方法是专门调用提供程序 API 并请求更多信息。 FullName 和 Link 的值由 Facebook 自动传递回。 一个 值,该值指示 Facebook 是否已通过调用 Facebook API 来验证帐户。 首先,填充 FullName 和 Link 的值,然后获取已验证的值。

若要检索其他用户数据,请在 Controllers 文件夹中打开 AccountController.cs 文件。

此文件包含用于记录、注册和管理帐户的逻辑。 具体而言,请注意名为 ExternalLoginCallbackExternalLoginConfirmation 的方法。 在这些方法中,可以添加代码来自定义应用程序的外部登录操作。 ExternalLoginCallback 方法的第一行包含:

AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
    Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

其他用户数据将传回从 VerifyAuthentication 方法返回的 AuthenticationResult 对象的 ExtraData 属性中。 Facebook 客户端在 ExtraData 属性中包含以下值:

  • id
  • name
  • 链接
  • gender
  • accesstoken

其他提供程序在 ExtraData 属性中具有类似但略有不同的数据。

如果用户是您的网站的新用户,您将检索一些附加数据,并将该数据传递到确认视图。 仅当用户不熟悉你的站点时,才运行 方法中的最后一个代码块。 替换以下行:

return View("ExternalLoginConfirmation", new RegisterExternalLoginModel 
{ 
    UserName = result.UserName, 
    ExternalLoginData = loginData 
});

替换为以下行:

return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
{
    UserName = result.UserName,
    ExternalLoginData = loginData,
    FullName = result.ExtraData["name"],
    Link = result.ExtraData["link"]
});

此更改仅包括 FullName 和 Link 属性的值。

ExternalLoginConfirmation 方法中,修改下面突出显示的代码以保存其他用户信息。

if (user == null)
{
    // Insert name into the profile table
    UserProfile newUser = db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
    db.SaveChanges();

    db.ExternalUsers.Add(new ExternalUserInformation 
    { 
        UserId = newUser.UserId, 
        FullName = model.FullName, 
        Link = model.Link 
    });
    db.SaveChanges();

    OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
    OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

    return RedirectToLocal(returnUrl);
}
else
{
    ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
}

调整视图

从提供程序检索的其他用户数据将显示在注册页中。

“视图/帐户” 文件夹中,打开 ExternalLoginConfirmation.cshtml。 在现有用户名字段下方,添加 FullName、Link 和 PictureLink 的字段。

<li>
    @Html.LabelFor(m => m.FullName)
    @Html.TextBoxFor(m => m.FullName)
</li>
<li>
    @Html.LabelFor(m => m.Link)
    @Html.TextBoxFor(m => m.Link)
</li>

现在,你已基本准备好运行应用程序,并使用保存的其他信息注册新用户。 您必须具有尚未向站点注册的帐户。 可以使用其他测试帐户,也可以删除要重用的帐户的 UserProfilewebpages_OAuthMembership 表中的行。 通过删除这些行,可确保再次注册帐户。

运行应用程序并注册新用户。 请注意,这次确认页包含更多值。

屏幕截图显示了在将 Facebook 帐户与应用关联后可以输入用户名和其他信息的位置。

完成注册后,关闭浏览器。 在数据库中查找,注意 ExtraUserInformation 表中的新值。

安装 Facebook API 的 NuGet 包

Facebook 提供了一个 API,你可以调用该 API 来执行操作。 可以通过定向发送 HTTP 请求或使用安装 NuGet 包来调用 Facebook API,以便发送这些请求。 本教程介绍了如何使用 NuGet 包,但安装 NuGet 包并不是必需的。 本教程演示如何使用 Facebook C# SDK 包。 还有其他 NuGet 包可帮助调用 Facebook API。

“管理 NuGet 包” 窗口中,选择 Facebook C# SDK 包。

安装包

你将使用 Facebook C# SDK 调用需要用户访问令牌的操作。 下一部分介绍如何获取访问令牌。

检索访问令牌

大多数外部提供程序在验证用户的凭据后传递回访问令牌。 此访问令牌非常重要,因为它使你能够调用仅对经过身份验证的用户可用的操作。 因此,当想要提供更多功能时,检索和存储访问令牌至关重要。

根据外部提供程序,访问令牌可能仅在有限的时间内有效。 为了确保拥有有效的访问令牌,每次用户登录时都会检索该令牌,并将其存储为会话值,而不是将其保存到数据库。

ExternalLoginCallback 方法中,访问令牌也会在 AuthenticationResult 对象的 ExtraData 属性中传递回。 将突出显示的代码添加到 ExternalLoginCallback ,以将访问令牌保存在 Session 对象中。 每次用户使用 Facebook 帐户登录时,都会运行此代码。

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
    AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
        Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
    if (!result.IsSuccessful)
    {
        return RedirectToAction("ExternalLoginFailure");
    }

    if (result.ExtraData.Keys.Contains("accesstoken"))
    {
        Session["facebooktoken"] = result.ExtraData["accesstoken"];
    }

    if (OAuthWebSecurity.Login(
        result.Provider, 
        result.ProviderUserId, 
        createPersistentCookie: false))
    {
        return RedirectToLocal(returnUrl);
    }

    if (User.Identity.IsAuthenticated)
    {
        // If the current user is logged in add the new account
        OAuthWebSecurity.CreateOrUpdateAccount(
            result.Provider,
            result.ProviderUserId, 
            User.Identity.Name);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        // User is new, ask for their desired membership name
        string loginData = OAuthWebSecurity.SerializeProviderUserId(
            result.Provider, 
            result.ProviderUserId);
        ViewBag.ProviderDisplayName =
            OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
        ViewBag.ReturnUrl = returnUrl;
        return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
        {
            UserName = result.UserName,
            ExternalLoginData = loginData,
            FullName = result.ExtraData["name"],
            Link = result.ExtraData["link"]
        });    
    }
}

尽管此示例从 Facebook 检索访问令牌,但可以通过名为“accesstoken”的同一密钥从任何外部提供程序检索访问令牌。

注销

默认 的 LogOff 方法将用户从应用程序中注销,但不会将用户从外部提供程序中注销。 若要将用户从 Facebook 注销并阻止令牌在用户注销后保留,可以将以下突出显示的代码添加到 AccountController 中的 LogOff 方法。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
    WebSecurity.Logout();
    if (Session["facebooktoken"] != null)
    {
        var fb = new Facebook.FacebookClient();
        string accessToken = Session["facebooktoken"] as string;
        var logoutUrl = fb.GetLogoutUrl(new { access_token = accessToken, next = "http://localhost:39852/" });

        Session.RemoveAll();
        return Redirect(logoutUrl.AbsoluteUri);
    }

    return RedirectToAction("Index", "Home");
}

在 参数中 next 提供的值是用户注销 Facebook 后要使用的 URL。 在本地计算机上进行测试时,需要为本地站点提供正确的端口号。 在生产环境中,需要提供默认页面,例如 contoso.com。

检索需要访问令牌的用户信息

存储访问令牌并安装 Facebook C# SDK 包后,可以将它们一起使用,从 Facebook 请求其他用户信息。 在 ExternalLoginConfirmation 方法中,通过传递访问令牌的值创建 FacebookClient 类的实例。 请求当前经过身份验证的用户的已 验证 属性的值。 verified 属性指示 Facebook 是否通过某些其他方式(例如向手机发送消息)验证了帐户。 将此值保存在数据库中。

if (user == null)
{
    // Insert name into the profile table
    UserProfile newUser = db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
    db.SaveChanges();

    bool facebookVerified;

    var client = new Facebook.FacebookClient(Session["facebooktoken"].ToString());
    dynamic response = client.Get("me", new { fields = "verified" });
    if (response.ContainsKey("verified"))
    {
        facebookVerified = response["verified"];
    }
    else
    {
        facebookVerified = false;
    }

    db.ExternalUsers.Add(new ExternalUserInformation 
    { 
        UserId = newUser.UserId, 
        FullName = model.FullName, 
        Link = model.Link, 
        Verified = facebookVerified 
    });
    db.SaveChanges();

    OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
    OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

    return RedirectToLocal(returnUrl);
}

再次需要为用户删除数据库中的记录,或使用其他 Facebook 帐户。

运行应用程序并注册新用户。 查看 ExtraUserInformation 表,查看 Verified 属性的值。

结论

在本教程中,你创建了一个与 Facebook 集成的站点,用于用户身份验证和注册数据。 你了解了为 MVC 4 Web 应用程序设置的默认行为,以及如何自定义该默认行为。