我在使用.NET Core 2.1项目中的Identity进行基于角色的授权的最佳设计方面遇到了问题 .
我已经使用ApplicationUser类从Identity扩展了User类 . 我需要5个不同的角色来控制对应用程序不同功能的访问:
管理员,教师,学生,家长和主管
所有常用属性都保存在User和ApplicationUser中,但我仍然需要与其他表的不同关系,具体取决于用户的角色 .
-
角色教师中的用户与1-N学校相关联
-
角色中的用户学生与1-N GroupOfStudents相关联(但不直接与学校相关)
-
角色中的用户父级链接到1-N学生(但不是学校)
-
......
另一个要求是用户必须能够处于1-N角色 .
在我的案例中,最佳做法是什么?
在身份的特征中是否有一些我缺少的东西?
我的想法最初是使用可空的FK,但随着角色数量的增加,为所有这些记录设置这么多空字段似乎不是一个好主意 .
我正在考虑使用“桥接表”将用户链接到每个角色的其他表 . 在ApplicationUser和桥接表之间 Build 多对多关系,并在桥接表和每个角色的各个表之间 Build 0-1关系 . 但这并不是真的有帮助,因为每个记录都会产生相同数量的空字段 .
我是.NET Core的新手,特别是Identity,我可能错过了一些关键词来进行有效的研究,因为它让我觉得它是一个非常基本的系统(在需求中没什么特别的花哨) .
谢谢阅读 !
EDIT : 在尝试深入项目之前,我没有试图找出最佳实践 . 因为's the first time I face that kind of requirement, I'm试图找到有关利弊的文件 .
我遵循了Marco的想法,并将继承用于我的基于角色的模型,因为这是我的第一个想法 . 我希望这有助于理解我的担忧 .
public class ApplicationUser : IdentityUser
{
public string CustomTag { get; set; }
public string CustomTagBis { get; set; }
}
public class Teacher : ApplicationUser
{
public string TeacherIdentificationNumber { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Student : ApplicationUser
{
public ICollection<StudentGroup> Groups { get; set; }
}
public class Parent : ApplicationUser
{
public ICollection<Student> Children { get; set; }
}
public class Course
{
public int Id { get; set; }
public string Title { get; set; }
public string Category { get; set; }
}
public class StudentGroup
{
public int Id { get; set; }
public string Name { get; set; }
}
这将创建一个数据库,其中包含一个包含所有属性的User的大表:
用户表生成
我可以使用它,它会工作 . 如果用户需要担任不同的角色,则可以填写任何可以为空的字段 .
我担心的是,对于每条记录,我都会有大量“不合适的字段”,这些字段仍然是空的 . 假设有1000名用户,80%的用户是学生 . 让800行包含以下内容的后果是: - 一个空的ParentId FK - 一个空的TeacherIdentificationNumber
这只是模型内容的一小部分 . 它没有“感觉”正确,我错了吗?
是不是有更好的方法来设计实体,以便表User只包含所有用户的公共属性(因为它应该?),并且仍然能够将每个用户链接到另一个将User连接到1的表-N表老师/学生/家长/ ...表?
每层次表的方法图
EDIT 2: 使用Marco的答案,我尝试使用Table-Per-Type方法 . 在修改我的上下文以实现Table-Per-Type方法时,我想在添加迁移时遇到此错误:
“实体类型'IdentityUserLogin'需要定义主键 . ”
我相信这是因为我删除了:
base.OnModelCreating(builder);
导致拥有此代码:
protected override void OnModelCreating(ModelBuilder builder)
{
//base.OnModelCreating(builder);
builder.Entity<Student>().ToTable("Student");
builder.Entity<Parent>().ToTable("Parent");
builder.Entity<Teacher>().ToTable("Teacher");
}
我相信那些身份密钥映射在base.OneModelCreating中 . 但即使我取消注释该行,我在数据库中保留相同的结果 .
经过一些研究,我发现this article帮助我完成了创建Table-per-type模型并应用迁移的过程 .
使用这种方法,我有一个如下所示的模式:Table-Per-Type方法
如果我错了,请纠正我,但这两种技术都符合我的要求,更多的是关于设计的偏好?它在架构和身份特征方面没有重大影响?
对于第三种选择,我想使用不同的方法,但我不太确定 .
Does a design like this could fit my requirements and is it valid? 有效,我的意思是,将教师实体与角色而不是用户相关联会感觉很奇怪 . 但在某种程度上,教师实体代表用户在教师角色中所需的功能 .
对实体的作用
我还不太清楚如何使用EF核实现它,以及如何重写IdentityRole类将影响Identity功能 . 我在上面但尚未弄明白 .
2 回答
我建议你利用asp.net核心的新功能和新的Identity框架 . 有很多关于security的文档 .
您可以使用policy based安全性,但在您的情况下resource-based安全性似乎更合适 .
最好的方法是不混合上下文 . 保持关注点:身份上下文(使用UserManager)和业务上下文(学校,您的DbContext) .
因为将ApplicationUser表放在您的“业务上下文”表示您直接访问Identity上下文 . 这不是你应该使用Identity的方式 . 使用UserManager进行与IdentityUser相关的查询 .
为了使其工作,而不是继承ApplicationUser表,在学校上下文中创建一个用户表 . 它不是副本而是新表 . 事实上,唯一的共同点是UserId字段 .
检查我的答案here,了解更详细的设计 .
将TeacherIdentificationNumber之类的字段移出ApplicationUser . 您可以将此作为声明添加到用户(AspNetUserClaims表):
或将其存储在学校环境中 .
另外角色考虑使用声明,您可以通过类型名称区分声明(例如http:// school1 .myapp.com / role):
虽然我认为在您的情况下,最好将信息存储在学校环境中 .
底线,保持身份上下文不变,并将表添加到学校上下文中 . 您不必创建两个数据库,只是不添加跨上下文关系 . 绑定这两者的唯一因素是UserId . 但是,您不需要实际的数据库关系 .
使用UserManager等进行身份查询以及您的应用程序的学校环境 . 如果不进行身份验证,则不应使用Identity上下文 .
现在,在设计中,创建一个具有匹配的UserId字段的用户表,以链接当前用户 . 仅当您要显示此信息时(在报告上),才添加名称等字段 .
为学生,教师等添加一个表,使用复合键:School.Id,User.Id . 或者添加一个公共ID并对School.Id,User.Id的组合使用唯一约束 .
当用户出现在表格中时,这意味着用户是学校的学生x或学校的老师 . 不需要Identity环境中的角色 .
使用导航属性,您可以轻松确定“角色”并访问该“角色”的字段 .
你所做的完全符合你的要求 . 您目前实施的内容称为
Table-Per-Hierarchy
. 这是Entity Framework在发现其模型时所采用的默认方法 .另一种方法是
Table-Per-Type
. 在这种情况下,Entity Framework将创建4个表 .用户表
学生表
教师表
父表
由于所有这些实体都继承自
ApplicationUser
,因此数据库将在它们与其父类之间生成FK关系 .要实现此功能,您需要修改DbContext:
这应该是最规范化的方法 . 然而,有第三种方法,你最终得到3个表,父ApplicationUser类将映射到它的具体实现 . 但是,我从来没有用Asp.Net Identity实现这个,所以我不知道它是否会起作用,如果你遇到一些关键的冲突 .