65.9K
CodeProject 正在变化。 阅读更多。
Home

扩展 MVC Identity

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (9投票s)

2014年7月5日

CPOL

7分钟阅读

viewsIcon

21780

downloadIcon

3

扩展 MVC Identity

引言

我最初写下这篇操作指南是为了提醒自己。我从经验和其他阅读材料(书籍和文章)中收集了其内容。然后我希望与大家分享,尽管有很多文章和操作指南都在讨论这个精彩的主题,但我仍希望它能对某人有所帮助。

让我们从头开始。

当你启动一个 Web 应用程序时,会弹出一个显示授权选项的屏幕。我不会涵盖这些选项,因为我在这里感兴趣的是“个人用户账户”。

你还会看到一个“更改”按钮,默认情况下会选中“个人用户授权”。保持不变,然后创建你的 MVC 应用程序。

一旦你的应用程序创建完成,你就会获得许多关于身份验证和授权的即用功能。要感受一下,点击“注册”链接并注册自己。然后应用程序将通过显示你的名字并向你问好来识别你。你可以点击它,你将被重定向到“管理账户”视图,以便更改密码。你也可以注销并再次登录。所有幕后工作都已为你完成。

我们刚刚使用的链接位于共享文件夹中的 _loginPartial 视图中。这些链接触发 AccountController 上的动作。

当然,如果应用程序记住你,那是因为某个地方存在持久化机制和存储用户信息的仓库。我们稍后会讲到。

现在,如果你想限制已认证用户对控制器任何动作或整个控制器的访问,你可以向其添加 [Authorize] 属性。当你尝试访问它时,如果你未登录,你将被重定向到登录视图。登录 URL 配置在 App_Start 文件夹中的 StartupAuth.cs 中。以下是代码片段:

app.UseCookieAuthentication(new CookieAuthenticationOptions
 {
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
   });

当然,你可以限制除你指定的用户之外的所有用户的访问,如下所示:

[Authorize(Users="m2a")] 或按角色或多个角色 [Authorize(Role="admin, techees")]

即使你在控制器级别添加了这些属性,你也可以通过添加限制较少的角色或属性(如 [AllowAnonymous])来允许访问任何动作。这是非常简单的内容,你可以轻松尝试。

数据库

正如我之前所说,在幕后,有一个数据库存储着用户信息。点击你的项目并“显示所有文件”。在 App-Data 文件夹下,你会看到一个 MDF 扩展名的文件,这就是数据库。

双击它,如果想浏览,它将在服务器资源管理器中打开。但是,像哈希密码这样的用户信息存储在 AppNetUser 表中。

我对此不喜欢的一点是数据库名称。这可能只是一个细节,但我希望名称比这个aspnet-thing+datetime...更有意义。所以要做到这一点,打开你的 Web config 文件并进入 connectionstring 部分。你会看到默认情况下连接字符串是 DefaultConnection。因此,在那里更改名称,并更改目录名称,使其对你来说有意义,然后重建应用程序。你会注意到创建了一个新数据库,你必须再次注册。要删除旧数据库,你需要从文件服务器和服务器资源管理器中进行操作。

核心身份

AspNet.Identity.Core 是幕后实现魔法的 API。它暴露了许多接口(仓储模式实现),如 IUserIRoleIUserStore 等。还有许多具体对象,如 UserManager RoleManager。我们稍后会介绍 UserManager 类,但它基本上暴露了管理用户信息(如密码哈希)的领域逻辑。还有一个 UserStore 可用于管理用户,但你应始终使用 UserManager,除非你需要低级别功能并需要 UserStore 来完成。例如,如果你需要控制用户信息的持久化方式或位置,你可能希望使用 MongoDb 或任何其他你选择的 NoSql 数据库来持久化用户。如果你选择坚持使用 SQL Server,那么 Entity Framework 就派上用场了。

实体框架

Identity.EntityFramework 程序集中,有许多对象可以方便用户和角色管理,例如 IdentityUser IdentityRole。你还有一个 IdentityDbContext ,它允许你与 SQL 数据库交互。

扩展 Identity

假设你想在登录视图中添加一个秘密问题,以便将其记录为安全措施。要做到这一点,打开 identityModel 类,并向 ApplicationUser 类添加一个属性,如下所示:

 public class ApplicationUser : IdentityUser
 {
     public string SecretQuestion { get; set; }
    public string SecretQuestionResponse { get; set; }
 }

现在你需要将它与登录视图连接起来,以便从用户那里获取信息。现在我们意识到我们还需要一些关于公司的有用信息,所以我们继续定义一个代理 poco,如下所示:

 public class Agency
 {
        public int Id { get; set; }
        public string Name { get; set; }
        public int NumberOfEmployees { get; set; }
    }

为了让这个 poco 与 Entity Framework 交互,我们需要一个引用该对象的 DbSet 类。我们添加它,如下所示:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
         public ApplicationDbContext()  : base("DefaultConnection")
          {
           }
        public DbSet<Agency> AgencyContext;
    }

ApplicationDbContext 获取 IdentityDbContext 类,该类提供对用户和角色管理以及 AgencyContext 的访问。这意味着无论你在何处实例化此对象,你都将完全控制它们。

但是请记住,就用户而言,UserManager 是一个更好的选择,正如我们前面所说的。同样,这取决于你尝试做什么。

现在,我们设置好了,运行应用程序。出现了臭名昭著的黄屏死机。如果你错过了,请查看图 1,了解它的样子。但基本上,它是在说你需要更新数据库,以处理你刚刚添加的新内容。

为此,我们需要使用包管理器控制台启用迁移(enable-migrations)。一旦添加,解决方案中会添加一个 Migrations 文件夹,其中包含一个配置类。

在配置类中,将 automaticMigrationEnabled 设置为 true ,以避免在开发过程中保留脚本。一旦你准备好在生产环境之前基准化数据库,你仍然需要管理这些脚本。

internal sealed class Configuration : DbMigrationsConfiguration<identity2.Models.ApplicationDbContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            ContextKey = "identity2.Models.ApplicationDbContext";
        }

        protected override void Seed(identity2.Models.ApplicationDbContext context)
        {

        }
    }

请注意那里的种子方法。此方法用于使用参考数据填充数据库。可能是用户、角色或任何你希望在数据库创建后立即存在的数据。

protected override void Seed(identity2.Models.ApplicationDbContext context)
 {

context.Users.AddOrUpdate(firstUser => firstUser.UserName, new ApplicationUser { UserName = "Mack", PasswordHash = new PasswordHasher().HashPassword("password") });
        }

对我来说,这种做法太Entity Framework化了。由于我在 AccountController 中有一个如何管理此事的示例,我将对其进行重构,如下所示:

protected override void Seed(identity2.Models.ApplicationDbContext context)
 {

  //context.Users.AddOrUpdate(firstUser => firstUser.UserName, new ApplicationUser { UserName = "Mack", PasswordHash = new PasswordHasher().HashPassword("password") });

  if (!context.Users.Any(user => string.Compare(user.UserName, "mack", StringComparison.CurrentCultureIgnoreCase) == 0))
            {
                var userstore = new UserStore<ApplicationUser>(context);
                var usermanager = new UserManager<ApplicationUser>(userstore);

                usermanager.Create(new ApplicationUser {UserName = "mack"}, "password");
            }
        }

如果你查看 AccountController 的构造函数,你会注意到 UserStore 需要一个上下文,而 UserManager 需要一个 UserStore

     public AccountController()
            : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
        }

我们刚刚在种子方法中完成了此操作,并将用户和密码添加到了创建函数中。现在继续通过管理器控制台更新数据库。

如果你想查看当有待应用的迁移时会发生什么,请在 update-database 之后添加 -verbose 标志。之后,我们再次运行应用程序,一切都按预期运行。

这是包含“admin”角色的更新代码:

protected override void Seed(identity2.Models.ApplicationDbContext context)
 {

//context.Users.AddOrUpdate(firstUser => firstUser.UserName, new ApplicationUser { UserName = "mack", PasswordHash = new PasswordHasher().HashPassword("password") });

 if (!context.Roles.Any(role => string.Compare(role.Name, "admin", StringComparison.CurrentCultureIgnoreCase) == 0))
 {
     var rolestore = new RoleStore<IdentityRole>(context);
     var rolemanager = new RoleManager<IdentityRole>(rolestore);
      rolemanager.Create(new IdentityRole { Name = "admin" });
  }

 if (!context.Users.Any(user => string.Compare(user.UserName, "mack", StringComparison.CurrentCultureIgnoreCase) == 0))
  {
     var userstore = new UserStore<ApplicationUser>(context);
     var usermanager = new UserManager<ApplicationUser>(userstore);
     var user = new ApplicationUser { UserName = "Mack" };
                usermanager.Create(user, "password");
                usermanager.AddToRole(user.Id, "admin");
            }
        }

正如你所注意到的,种子方法还有重构空间。因为如果你需要添加超过 100 个用户,你不会希望重复相同的代码超过 100 次。

外部登录

外部登录使用 OpenId 或 OAuth 等协议提供用户身份。你在提供商网站上注册应用程序,然后获得一个密钥添加到你的应用程序中。

身份提供者如何向你提供用户身份对我来说有点模糊。

它足够复杂,因此在此处避免讨论,而且我当然不理解“执行发现”操作和 Google 的 xrds 文档... 无论如何,如果你有兴趣学习更多,外面有很多资料。

尽管如此,为用户提供此机会的优势在于他们不必记住另一个密码,而作为开发者,最大的优势在于你不必担心存储和管理用户凭据等。但你仍然必须信任 Microsoft、Google、Facebook、Twitter 等提供商。

要启用此功能,请取消注释 Startup 对象(在 Startup.Auth.cs 文件中)中的代码。请注意,Google 不需要任何密钥或注册。取消注释代码如下:

public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
     app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                 LoginPath = new PathString("/Account/Login")
 });
// Use a cookie to temporarily store information about a user logging in with a third party login provider
 app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");

            //app.UseTwitterAuthentication(
            //   consumerKey: "",
            //   consumerSecret: "");

            //app.UseFacebookAuthentication(
            //   appId: "",
            //   appSecret: "");

      app.UseGoogleAuthentication();
  }

然后点击登录视图上的Google按钮。按照说明操作,最后你将被重定向为应用程序中的已识别用户。请注意,你甚至可以在著名的“你好”问候语之后更改你显示的姓名!

幕后 OWIN 正在发挥作用。要对此有所了解,请返回到 AccountController 并查看 AuthenticationManager 属性。它来自 Owin Context。

要查看你用来登录 Google 的电子邮件地址(例如),请转到 ExternalLoginCallback 函数并在开头编写以下代码:

var fromGoogle=  await AuthenticationManager.AuthenticateAsync(DefaultAuthenticationTypes.ExternalCookie);

现在您可以浏览到 Identity,然后到 EmailAddress Claim 以获取电子邮件地址。

© . All rights reserved.