如何有条件地将多个模型映射到 ASP.NET Core Identity 中的一个表(AspNetUsers)?

Posted

技术标签:

【中文标题】如何有条件地将多个模型映射到 ASP.NET Core Identity 中的一个表(AspNetUsers)?【英文标题】:How to conditionally map multiple models to one table (AspNetUsers) in ASP.NET Core Identity? 【发布时间】:2021-04-10 01:39:00 【问题描述】:

我正在 ASP.NET Core MVC 中创建一个学习评估 Web 应用程序。 我计划拥有两个独立的数据库:一个用于使用 Identity Framework 的用户,另一个用于使用 Dapper 的数据。但是,我的模型与我的用户有关系(InstructorStudent)。这是我绘制的显示关系的实体关系图:

例如,我必须为每个 Student 实例包含一个 Course 导航属性。同样,TestInstance 包含InstructorStudent 的导航属性,明确区分了两个用户类。 我还需要另一个名为 Administrator 的用户类:

User Class Responsibilities
Student Attends tests
Instructor Creates and schedules tests
Administrator Manages instructor and student accounts and courses. Has all the privileges of an instructor

我知道可以通过从IdentityUser 派生在 Identity 中创建自定义属性,但我需要维护三个不同的用户类,而 Identity 只提供一个名为 AspNetUsers 的表。我不想在我的域模型中直接使用 Identity 中的表。事实上,如果 Identity 可以拥有自己的独立数据库,那就太好了。管理员将是创建和管理教师和学生帐户的人。

我的问题是:

有什么方法可以创建三个模型/实体 StudentInstructorAdminstrator 作为我的域模型的一部分,然后让它们都使用 Identity 的 AspNetUsers 表中的 ID ? 他们的角色信息可以存储为声明或 Identity 提供的 AspNetUserRoles 表中。

然后我可以使用 Identity 添加策略来实施访问控制。

我希望这不是设计缺陷。如果是这样,我仍然可以重新考虑设计,因为我的项目处于初始阶段。

【问题讨论】:

StudentInstructorAdministrator 可以是AspNetUsers 的扩展吗?然后AspNetUsers 可以有重复的列,如IDNameDOBEmailAdministrator 可以有AspNetUserID 和管理员类的其余列。 @BrunoSerrano 是的,它们基本上是AspNetUsers 的扩展。一个简单的表继承就可以完成这项工作。如果我可以至少在应用程序中在三个单独的模型(类)中表示这些用户,那就太好了,然后我可以轻松地在模型中包含导航属性,例如 TestInstance(在我的 ER 图中),其中包含对两个用户的引用类。 【参考方案1】:

您可以将 TPT 与 EF Core 5 一起使用 例如,此项目将 TPT 与 ASP.NET Core Identity 集成。

https://github.com/ArminShoeibi/SchoolManagementSystem

 public class ApplicationUser : IdentityUser<Guid>
    
        public string FirstName  get; set; 
        public string LastName  get; set; 
        public string NationalCode  get; set; 
        public DateTime BirthDate  get; set; 
        public Gender Gender  get; set; 

    

 public class Student : ApplicationUser
    
        public string FatherName  get; set; 
        public string PlaceOfBirth  get; set; 
        public string Address  get; set; 
        public string HomePhoneNumber  get; set; 
    

 public class Teacher : ApplicationUser
    
        public string FieldOfStudy  get; set; 
        public AcademicDegree AcademicDegree get; set; 
        public int YearsOfExperience  get; set; 
    


  public class SchoolMgmtContext : IdentityDbContext<ApplicationUser,ApplicationRole,Guid>
    


        public SchoolMgmtContext(DbContextOptions<SchoolMgmtContext> dbContextOptions)
            :base(dbContextOptions)
        

        

        public DbSet<Teacher> Teachers  get; set; 
        public DbSet<Student> Students  get; set; 

        protected override void OnModelCreating(ModelBuilder builder)
        
            base.OnModelCreating(builder);
            builder.Entity<ApplicationUser>().ToTable("Users");
            builder.Entity<ApplicationRole>().ToTable("Roles");

            builder.Entity<Student>().ToTable("Students");
            builder.Entity<Teacher>().ToTable("Teachers");
         

        
    

结果是:

【讨论】:

谢谢!如果您能在此处简要描述该过程以及执行此操作的代码段,那就太好了。【参考方案2】:

讲师也可以是管理员吗?如何处理这种多角色授权?

也许这在本例中无关紧要,但我喜欢考虑这些质量/架构问题,然后再因“新见解”而遇到用户提出的更改请求。

就个人而言,我会考虑创建一种Role 表,其中包含三个角色。使用单独的UserRole 表,您可以在用户及其角色之间创建多对多关系。因此,我将在这里使用一种组合,而不是使用继承/派生。

我认为您当前使用AspNetUsers 的逻辑将保持不变。或许您可以使用 Identity 添加策略,以基于这两个额外的角色相关表实现访问控制?

编辑

您还可以更进一步,创建一个额外的 Right 表,其中包含单独的用户权限,例如参加测试、创建测试、安排测试、管理帐户等。您可以创建一个单独的 RoleRight 表来将这些权限分配给所需的角色。

这将使您的角色更具动态性,甚至可能由用户配置。 (当然,在后一种情况下,只有用户有权创建和管理角色。)

然后在您的代码中,您可以检查用户是否具有分配的特定权限(按其角色)。例如,在负责创建测试的代码中,您实际上可能想要检查用户是否有权创建测试,而不是明确检查用户是否是讲师。

无论如何,这只是我对这个问题的 50 美分。希望对您有所帮助。

【讨论】:

Identity 已经有名为 AspNetRolesAspNetUserRoles 的表,我可能计划将它们用于访问控制。另外,我真的很喜欢Right table apporach,它会增加很多灵活性。在我的例子中,Administrator 拥有Instructor 的所有权限以及管理教师和学生帐户的权限。【参考方案3】:

我认为您可以通过创建一个用户表来轻松解决这个问题,该表是所有可能访问系统的系统用户(学生、教师、管理员)的清单,其中包含 ID、姓名、电子邮件地址等共享属性等(请务必对您存储的任何密码加盐!)

那么您有两个前进的选择:您可以使用此处的多表方法,只需让每个“角色表”通过 userId 连接到用户表,并将唯一属性存储到该角色:在您的示例中,学生表可能只是一个课程 ID 和一个用户 ID。这使您可以在发现每个角色时使用其他属性自定义它们。

或者,您可以将角色组合到一个表中,但除非您做一些工作来抽象独特的属性,否则您最终可能会得到一个非常宽的表,其中包含许多不适用的属性的空白值到每一个角色。不要这样做。一开始很容易,但随着你的应用程序的增长,这个表会变得一团糟。

只需添加一个“用户”表,您就可以在对现有架构进行最小更改的情况下进行改进。

另外一点:学生和课程之间是一对一的关系。我可能是错的,但您可能希望这是一对多的,因为大多数学生一次会学习多门课程。您的里程可能会有所不同。

【讨论】:

我想我会采用多表方法,并将 Identity 留给一个 AspNetUsers 表。 Identity 还生成AspNetRoles 表和一个名为AspNetUserRoles 的连接表,我计划将其用于分配角色。但是,问题是我无法真正弄清楚如何将StudentAdministratorInstructor 模型/表映射到 Identity 用于存储用户的表:AspNetUsers【参考方案4】:

因为紧密集成

我想提出紧密集成的存在是核心问题。如果您的设计没有创建紧密集成,那么问题就会消失。您可以确定您的设计的集成程度。

关注点分离

对于为 Identity 维护一个单独的数据库和为应用程序本身维护一个数据库,有很好的论据和实际用例。我最近开发并部署了这样一个应用程序,我对结果非常满意。

第一个原因是“当另一个应用程序添加到组合中时会发生什么?”。在野外,身份服务往往是孤立的:Azure AD、Google Cloud Identity、AWS 身份和访问管理等。除了固有的安全优势外,身份服务将支持您的所有应用程序,现在和未来,不应在一个特定应用程序中紧密耦合。

您的应用程序是基于网络的:当用户需要移动应用程序时会发生什么?如果 Identity 只能通过 Web 应用程序访问,那么现在该怎么办? Web 应用程序可能使用基于会话的身份验证,因此您需要重写所有这些以支持移动应用程序。不会很漂亮。

另一方面,假设您分离了服务。此外,由于您的设计要求身份服务与平台无关,因此您实现了对身份验证的 JWT(JSON Web 令牌)支持。在这种情况下,移动应用程序的身份验证将完全相同,没有任何变化

让我们将这个场景更进一步:您的应用程序的用户已经厌倦了点击“刷新”来查看他们的测试成绩是否可用(拉取)。他们要求在准备好考试成绩时通知(推送)。

移动推送通知的实现与应用程序的核心功能无关;它是以用户/平台为中心的功能。如果您不相信这是真的,请考虑使用移动设备作为第二个因素的 2FA(双因素身份验证)。

身份从来都不是一个试验的好地方,而且可以说是你最不想犯错的地方。我相信您在身份解决方案中添加或调整的所有内容都可能增加攻击向量。这是 KISS (Keep It Simple Stupid) 的教科书示例。

将用户连接到身份

User 表指的是StudentTeacher 表; Identity 表指的是AspNetUsers

如您所说,如果表位于单个数据库中,它们将使用我们都知道和喜爱的标准“开箱即用”外键引用进行连接。

这是有趣的部分:我们可以自己动手了!从根本上说,外键是一个非常基本的概念:这是一个 id,用它来引用一些东西。这是外键引用实现的唯一要求。

请记住:UserIdentity 相同。系统通常会包含永远不会登录其系统的Users 的数据。此外,站点管理员很可能不是TeacherStudent

对于标准用例,我的建议是首先创建User 实体,然后是Identity。然后使用您刚刚创建的Identity 的ID 更新User 表中的可选列。

您想为每个Identity 添加一个角色或UserType 声明(讲师、学生),这样您就知道您会在哪些User 表中找到User

【讨论】:

我同意你的看法。我想通过从我的核心域模型中隔离身份来放松耦合。我想知道是否有一种方法可以告诉StudentInstructorAdministrator 模型根据它们在AspNetRoles 表中的角色使用AspNetUsers 表中的ID,我认为' d 解决问题。管理员用户将创建和管理学生和教师帐户。欢迎任何替代解决方案。 我编辑了我的答案来解决您的问题。请看一看。

以上是关于如何有条件地将多个模型映射到 ASP.NET Core Identity 中的一个表(AspNetUsers)?的主要内容,如果未能解决你的问题,请参考以下文章

将表数据映射到 asp .Net MVC 中的 ViewModel 列表

带有现有存储库的 ASP.NET mvc 脚手架并将 POCO 映射到视图模型

如何安全地将 EC2 托管的 Shiny 应用程序集成到 asp.net 项目中

ASP.NET Core 中的模型绑定将下划线映射到标题大小写属性名称

如何有效地将巨大的 sql 表加载到 asp.net 应用程序内存中以进一步迭代它?

使用 Fluent NHibernate 有条件地将两列映射到一个字段