ASP.NET MVC 5 Identity:实现基于组的权限管理(二)
这是本系列文章的第二部分,共两部分。在本文中,我们将探讨如何使用 ASP.NET MVC 5 Identity 系统实现一个基本的基于组的权限管理系统。在此系列中,我们将在前一部分的基础上,使用我们扩展 IdentityUser 类和实现基于角色的(权限)概念。
这是本系列文章的第二部分,共两部分。在本文中,我们将探讨如何使用 ASP.NET MVC 5 Identity 系统实现一个基本的基于组的权限管理系统。在此系列中,我们将建立在之前我们用于扩展 IdentityUser 类和实现基于角色的应用程序安全性的基础上,以及用于扩展和自定义 IdentityRole 类的基础上。
在本系列中,我们希望克服简单“用户和角色”安全模型的一些局限性,而是将角色(“权限”)分配给组,然后将一个或多个组分配给每个用户。
在第一部分中,我们探讨了如何为扩展 ASP.NET Identity 系统以实现基本的基于组的权限管理模式来建模我们的核心域对象。我们决定将组分配给各种权限组合,并将用户分配给一个或多个组。“权限”在这里是指 Identity 系统提供的熟悉的“角色”,MVC 授权系统依赖它来进行用户身份验证和应用程序访问授权。
更新于 2014/8/11:此项目适用于 Identity 1.0。如果您使用的是 Identity 2.0,此处代码将无法正常工作。请参阅 ASP.NET Identity 2.0:实现基于组的权限管理
<--- 回顾第一部分:扩展模型
到目前为止,我们已经通过添加 Group 类扩展了我们的域模型,实现了用户与组之间,以及组与角色(“权限”)之间的多对多关系。我们创建了
构建示例应用程序 - 控制器、视图和视图模型
接续上文,现在我们需要添加示例应用程序的功能组件。显然,我们需要一些控制器和视图,但在构建这些之前,我们将添加一些将由各个控制器使用并传递给视图的视图模型。
添加组视图模型
我们需要一些新的视图模型来完成组的实现。此外,我们不再需要 SelectUserRolesViewModel
。现在我们将用户分配给组,而不是直接分配给角色,因此我们可以删除该代码。为简单起见,我们将所有新视图模型添加到 AccountViewModels.cs 文件中,然后逐一解释它们的用途。
首先,打开 AccountViewModels.cs 文件,找到 SelectUserRolesViewModel
的代码并删除它。
接下来,在 AccountViewModels.cs 文件的末尾添加以下新视图模型
添加新的必需视图模型
// Wrapper for SelectGroupEditorViewModel to select user group membership:
public class SelectUserGroupsViewModel
{
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<SelectGroupEditorViewModel> Groups { get; set; }
public SelectUserGroupsViewModel()
{
this.Groups = new List<SelectGroupEditorViewModel>();
}
public SelectUserGroupsViewModel(ApplicationUser user)
: this()
{
this.UserName = user.UserName;
this.FirstName = user.FirstName;
this.LastName = user.LastName;
var Db = new ApplicationDbContext();
// Add all available groups to the public list:
var allGroups = Db.Groups;
foreach (var role in allGroups)
{
// An EditorViewModel will be used by Editor Template:
var rvm = new SelectGroupEditorViewModel(role);
this.Groups.Add(rvm);
}
// Set the Selected property to true where user is already a member:
foreach (var group in user.Groups)
{
var checkUserRole =
this.Groups.Find(r => r.GroupName == group.Group.Name);
checkUserRole.Selected = true;
}
}
}
// Used to display a single group with a checkbox, within a list structure:
public class SelectGroupEditorViewModel
{
public SelectGroupEditorViewModel() { }
public SelectGroupEditorViewModel(Group group)
{
this.GroupName = group.Name;
this.GroupId = group.Id;
}
public bool Selected { get; set; }
[Required]
public int GroupId { get; set; }
public string GroupName { get; set; }
}
public class SelectGroupRolesViewModel
{
public SelectGroupRolesViewModel()
{
this.Roles = new List<SelectRoleEditorViewModel>();
}
// Enable initialization with an instance of ApplicationUser:
public SelectGroupRolesViewModel(Group group)
: this()
{
this.GroupId = group.Id;
this.GroupName = group.Name;
var Db = new ApplicationDbContext();
// Add all available roles to the list of EditorViewModels:
var allRoles = Db.Roles;
foreach (var role in allRoles)
{
// An EditorViewModel will be used by Editor Template:
var rvm = new SelectRoleEditorViewModel(role);
this.Roles.Add(rvm);
}
// Set the Selected property to true for those roles for
// which the current user is a member:
foreach (var groupRole in group.Roles)
{
var checkGroupRole =
this.Roles.Find(r => r.RoleName == groupRole.Role.Name);
checkGroupRole.Selected = true;
}
}
public int GroupId { get; set; }
public string GroupName { get; set; }
public List<SelectRoleEditorViewModel> Roles { get; set; }
}
public class UserPermissionsViewModel
{
public UserPermissionsViewModel()
{
this.Roles = new List<RoleViewModel>();
}
// Enable initialization with an instance of ApplicationUser:
public UserPermissionsViewModel(ApplicationUser user)
: this()
{
this.UserName = user.UserName;
this.FirstName = user.FirstName;
this.LastName = user.LastName;
foreach (var role in user.Roles)
{
var appRole = (ApplicationRole)role.Role;
var pvm = new RoleViewModel(appRole);
this.Roles.Add(pvm);
}
}
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<RoleViewModel> Roles { get; set; }
}
添加组控制器
在我们可以对应用程序进行更多操作之前,我们需要添加一个组控制器和相关的视图。我们已经有了一个来自前一篇文章的角色控制器,现在让我们为组添加一个。我们从一个相当标准的 CRUD 型控制器开始,可能由 Visual Studio 生成
基本的组控制器
public class GroupsController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
[Authorize(Roles = "Admin, CanEditGroup, CanEditUser")]
public ActionResult Index()
{
return View(db.Groups.ToList());
}
[Authorize(Roles = "Admin, CanEditGroup, CanEditUser")]
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Group group = db.Groups.Find(id);
if (group == null)
{
return HttpNotFound();
}
return View(group);
}
[Authorize(Roles = "Admin, CanEditGroup")]
public ActionResult Create()
{
return View();
}
[Authorize(Roles = "Admin, CanEditGroup")]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Name")] Group group)
{
if (ModelState.IsValid)
{
db.Groups.Add(group);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(group);
}
[Authorize(Roles = "Admin, CanEditGroup")]
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Group group = db.Groups.Find(id);
if (group == null)
{
return HttpNotFound();
}
return View(group);
}
[Authorize(Roles = "Admin, CanEditGroup")]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Name")] Group group)
{
if (ModelState.IsValid)
{
db.Entry(group).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(group);
}
[Authorize(Roles = "Admin, CanEditGroup")]
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Group group = db.Groups.Find(id);
if (group == null)
{
return HttpNotFound();
}
return View(group);
}
[Authorize(Roles = "Admin, CanEditGroup")]
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Group group = db.Groups.Find(id);
var idManager = new IdentityManager();
idManager.DeleteGroup(id);
return RedirectToAction("Index");
}
[Authorize(Roles = "Admin, CanEditGroup")]
public ActionResult GroupRoles(int id)
{
var group = db.Groups.Find(id);
var model = new SelectGroupRolesViewModel(group);
return View(model);
}
[HttpPost]
[Authorize(Roles = "Admin, CanEditGroup")]
[ValidateAntiForgeryToken]
public ActionResult GroupRoles(SelectGroupRolesViewModel model)
{
if (ModelState.IsValid)
{
var idManager = new IdentityManager();
var Db = new ApplicationDbContext();
var group = Db.Groups.Find(model.GroupId);
idManager.ClearGroupRoles(model.GroupId);
// Add each selected role to this group:
foreach (var role in model.Roles)
{
if (role.Selected)
{
idManager.AddRoleToGroup(group.Id, role.RoleName);
}
}
return RedirectToAction("index");
}
return View();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
到目前为止,上面的代码大部分应该看起来相当熟悉。我们有基本的创建/编辑/删除和索引方法,以及类末尾一个不寻常的方法 GroupRoles
。实际上,这看起来也应该很熟悉。这是我们之前项目中用于选择单个用户角色的代码的一个简单改编。在这里,我们改为对特定组执行相同操作。
为组控制器添加视图
以下是我们需要的组控制器视图。这些也相当标准,除了 GroupRoles 视图。不过,为了完整起见,我们将在此处包含所有内容。
最值得关注的是 GroupRoles
视图,所以我们将从那里开始。
GroupRoles 视图
在此视图中,我们将为特定组分配一个或多个角色。我们希望显示当前选定组的通用信息,然后显示所有可用角色的列表,并带有复选框以指示选中状态。对于我们的表示层,我们将角色描述为“权限”,以便概念对用户更清晰:用户是组的成员,组具有权限集。
这里,我们再次使用了一个 EditorTemplate(我们之前项目版本中的 SelectRoleEditorTemplate
),以便为每行项目渲染一个带复选框的 HTML 表格,以指示选择状态。此视图几乎与我们在此项目先前版本中用于 UserRoles
视图的视图相同(事实上,我只是对那个视图做了一些快速更改,然后重命名了它)。。
GroupRoles 视图
@model AspNetGroupBasedPermissions.Models.SelectGroupRolesViewModel
@{ ViewBag.Title = "Group Role Permissions"; }
<h2>Permissions for Group @Html.DisplayFor(model => model.GroupName)</h2>
<hr />
@using (Html.BeginForm("GroupRoles", "Groups", FormMethod.Post, new { encType = "multipart/form-data", name = "myform" }))
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.ValidationSummary(true)
<div class="form-group">
<div class="col-md-10">
@Html.HiddenFor(model => model.GroupName)
</div>
</div>
<div class="form-group">
<div class="col-md-10">
@Html.HiddenFor(model => model.GroupId)
</div>
</div>
<h4>Select Role Permissions for @Html.DisplayFor(model => model.GroupName)</h4>
<br />
<hr />
<table>
<tr>
<th>
Select
</th>
<th>
Permissions
</th>
</tr>
@Html.EditorFor(model => model.Roles)
</table>
<br />
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
在上文中,@html.EditorFor(model => model.Roles)
行导致 MVC 框架从 Views/Shared/EditorTempates/ 目录中找到我们(仔细命名的!!)SelectRoleEditorViewModel
,并使用它将每个角色项渲染为表格行。
如果您一直在关注这个“系列”文章,现在这应该是熟悉的地形了。
从这里开始,其余视图都相当标准。
索引组视图
索引组视图代码
@model IEnumerable<AspNetGroupBasedPermissions.Models.Group>
@{ ViewBag.Title = "Index"; }
<h2>Groups</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
@Html.ActionLink("Permissions", "GroupRoles", new { id=item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
请注意,我们在每行末尾添加了三个 ActionLinks - “编辑”、“权限”和“删除”。
这些将链接到 RolesController
上的相应方法。特别值得关注的是“权限”链接,它将引导我们到 GroupRoles
方法,并允许我们为每个组分配一个或多个角色(“权限”)。可以说,这是我们授权管理的业务核心。
创建组视图
创建组视图
@model AspNetGroupBasedPermissions.Models.Group
@{ ViewBag.Title = "Create Groups"; }
<h2>Create a new Group</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Group</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts { @Scripts.Render("~/bundles/jqueryval") }
编辑组视图
编辑组视图代码
@model AspNetGroupBasedPermissions.Models.Group
@{ ViewBag.Title = "Edit"; }
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Group</h4>
<hr />
@Html.ValidationSummary(true)
@Html.HiddenFor(model => model.Id)
<div class="form-group">
@Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts { @Scripts.Render("~/bundles/jqueryval") }
删除组视图
删除组视图代码
@model AspNetGroupBasedPermissions.Models.Group
@{ ViewBag.Title = "Delete"; }
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Group</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
</dl>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
@Html.ActionLink("Back to List", "Index")
</div>
}
</div>
更新 AccountController 以分配用户到组
现在我们有了组控制器和视图,我们需要更新原始 AccountController
上的代码。之前,我们在 AccountController
上创建了一个 UserRoles
方法,通过该方法将一个或多个角色分配给特定用户。现在,取而代之的是,我们将把一个或多个组分配给特定用户。
打开 AccountController
文件,删除 UserRoles
方法,并用以下 UserGroups
代码替换它
AccountController 的 UserGroups 方法
[Authorize(Roles = "Admin, CanEditUser")]
public ActionResult UserGroups(string id)
{
var user = _db.Users.First(u => u.UserName == id);
var model = new SelectUserGroupsViewModel(user);
return View(model);
}
[HttpPost]
[Authorize(Roles = "Admin, CanEditUser")]
[ValidateAntiForgeryToken]
public ActionResult UserGroups(SelectUserGroupsViewModel model)
{
if (ModelState.IsValid)
{
var idManager = new IdentityManager();
var user = _db.Users.First(u => u.UserName == model.UserName);
idManager.ClearUserGroups(user.Id);
foreach (var group in model.Groups)
{
if (group.Selected)
{
idManager.AddUserToGroup(user.Id, group.GroupId);
}
}
return RedirectToAction("index");
}
return View();
}
请注意,在上面的代码中,当 HTTP POST 从视图返回时,我们需要清除所有用户组的分配,然后将用户单独添加到 ViewModel 中选定的每个组。
将 UserRoles 视图替换为 UserGroups 视图
有了 AccountController
上新的 UserGroups
方法后,我们需要将以前的 UserRoles
视图替换为一个非常相似的 UserGroups
视图。与之前一样,我们将显示特定用户的基本用户信息,以及用户可能被分配到的可用组列表。再次使用带复选框的表格,我们可以将用户分配给一个或多个组。
与已弃用的 UserRoles
视图以及新添加的 GroupRoles
视图一样,我们需要一个特殊的 Editor Template ViewModel 和一个同名的 Editor Template View 来表示表格中的每个组。我们已经将 SelectGroupEditorViewModel
添加到了 AccountViewModels.cs 文件中。现在我们需要添加相应的 Editor Template View。
将以下视图添加到 Views/Shared/EditorTemplates/ 目录。请务必将其命名为 SelectGroupEditorViewModel
,使其与它所代表的 ViewModel 的名称完全匹配
SelectGroupEditorViewModel 视图
@model AspNetGroupBasedPermissions.Models.SelectGroupEditorViewModel
@Html.HiddenFor(model => model.GroupId)
<tr>
<td style="text-align:center">
@Html.CheckBoxFor(model => model.Selected)
</td>
<td style="padding-right:20px">
@Html.DisplayFor(model => model.GroupName)
</td>
</tr>
现在,完成后,从 Views/Account/ 目录中删除旧的 UserRoles
视图,并添加一个名为 UserGroups
的新视图,如下所示
UserGoups 视图
@model AspNetGroupBasedPermissions.Models.SelectUserGroupsViewModel
@{ ViewBag.Title = "User Groups"; }
<h2>Groups for user @Html.DisplayFor(model => model.UserName)</h2>
<hr />
@using (Html.BeginForm("UserGroups", "Account", FormMethod.Post, new { encType = "multipart/form-data", name = "myform" }))
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
@Html.ValidationSummary(true)
<div class="form-group">
<div class="col-md-10">
@Html.HiddenFor(model => model.UserName)
</div>
</div>
<h4>Select Group Assignments</h4>
<br />
<hr />
<table>
<tr>
<th>
Select
</th>
<th>
Group
</th>
</tr>
@Html.EditorFor(model => model.Groups)
</table>
<br />
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
更新 Account/Index 视图上的操作链接
我们需要对 Account Index 视图进行 minor 更新。目前,表格中每个用户旁边的链接指示“角色”并指向(现在不存在的)UserRoles 方法。相反,我们将显示文本“组”,并将链接指向新添加的 UserGroups 方法。修改后的代码应如下所示
更新 Account/Index 视图中表格项的操作链接
@model IEnumerable<AspNetGroupBasedPermissions.Models.EditUserViewModel>
@{ ViewBag.Title = "Index"; }
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Register")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.UserName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstName)
</th>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.Email)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.UserName }) |
@Html.ActionLink("Groups", "UserGroups", new { id = item.UserName }) |
@Html.ActionLink("Delete", "Delete", new { id = item.UserName })
</td>
</tr>
}
</table>
更新 _Layout.cshtml 上的导航链接
现在我们只需要确保我们可以访问我们刚刚构建到应用程序中的所有新功能。在 _Layout.cshtml 文件的代码中间,我们需要更新导航链接以匹配
更新 _Layout.cshtml 上的导航链接
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
<li>@Html.ActionLink("Users", "Index", "Account")</li>
<li>@Html.ActionLink("Groups", "Index", "Groups")</li>
<li>@Html.ActionLink("Permissions", "Index", "Roles")</li>
</ul>
@Html.Partial("_LoginPartial")
</div>
设置初始授权权限
此处代码中不明显(除非您克隆了完整项目),我们正在将新的用户/组/权限模型实现到我们刚刚创建的控制器上。鉴于我在 Migrations Configuration 文件中包含的示例角色(“权限”)中(我们用它来“播种”数据库),我使用以下权限方案设置了初始方法级别的 [Authorize]
属性。如果还没有,请将以下 [Authorize]
属性添加到每个控制器上的相应方法。替换掉上一个项目中存在的任何现有属性。
Account Controller [Authorize] 角色
操作方法 |
允许的角色 |
---|---|
登录 |
[AllowAnonymous] |
Register |
[Authorize(Roles = "Admin, CanEditUser")] |
Manage |
[Authorize(Roles = "Admin, CanEditUser, User")] |
目录 |
[Authorize(Roles = "Admin, CanEditGroup, CanEditUser")] |
未使用。 |
[Authorize(Roles = "Admin, CanEditUser")] |
删除 |
[Authorize(Roles = "Admin, CanEditUser")] |
UserGroups |
[Authorize(Roles = "Admin, CanEditUser")] |
Groups Controller [Authorize] 角色
操作方法 |
允许的角色 |
---|---|
目录 |
[Authorize(Roles = "Admin, CanEditGroup, CanEditUser")] |
详细说明 |
[Authorize(Roles = "Admin, CanEditGroup, CanEditUser")] |
Create |
[Authorize(Roles = "Admin, CanEditGroup")] |
未使用。 |
[Authorize(Roles = "Admin, CanEditGroup")] |
删除 |
[Authorize(Roles = "Admin, CanEditGroup")] |
GroupRoles |
[Authorize(Roles = "Admin, CanEditGroup")] |
Roles Controller [Authorize] 角色
操作方法 |
允许的角色 |
---|---|
目录 |
[Authorize(Roles = "Admin, CanEditGroup, CanEditUser")] |
Create |
[Authorize(Roles = "Admin")] |
未使用。 |
[Authorize(Roles = "Admin")] |
删除 |
[Authorize(Roles = "Admin")] |
正如我们所见,我已经尽力(在空间和这个已经很长的示例项目的实际限制内)构建了一个分层授权方案,适度遵循了“最小权限原则”。在生产应用程序中,我们可以假设您将拥有额外的业务域,并且需要仔细考虑角色权限的分配。
运行应用程序
如果我们启动应用程序,应该会看到标准的登录屏幕。登录后,如果导航到“组”链接,我们应该会看到我们用来播种数据库的组列表
组屏幕
如果我们点击“权限”链接,我们会发现当前分配给特定组的角色,或“权限”
权限屏幕
如果我们导航到用户屏幕,我们会看到我们所期望的——用户列表,以及深入查看特定用户属于哪些组的选项
用户屏幕
不过,一个有用的功能是为每个列出的用户添加一个额外的链接,通过该链接我们可以看到他们由于参与的所有组而获得的权限。
添加用户有效权限视图
幸运的是,我们可以做到这一点。首先,我们需要在 Accounts/Index 视图中添加一个链接。在底部附近,当我们设置表格数据每行旁边的链接时,像下面这样为“有效权限”添加链接
将有效权限链接添加到 Account/Index 视图
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.UserName }) |
@Html.ActionLink("Groups", "UserGroups", new { id = item.UserName }) |
@Html.ActionLink("Effective Permissions", "UserPermissions", new { id = item.UserName }) |||
@Html.ActionLink("Delete", "Delete", new { id = item.UserName })
</td>
我们已经在AccountViewModels.cs 中添加了 UserPermissionsViewModel
,所以现在我们只需要在 Views/Account 目录中添加一个 UserPermissions
视图
UserPermissions 视图
@model AspNetGroupBasedPermissions.Models.UserPermissionsViewModel
@{ ViewBag.Title = "UserPermissions"; }
<h2>Effective Role Permissions for user: @Html.DisplayFor(model => model.UserName)</h2>
<hr />
<table class="table">
<tr>
<th>
Role Permission
</th>
<th>
Description
</th>
<th></th>
</tr>
@foreach (var item in Model.Roles)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.RoleName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
</tr>
}
</table>
<div>
@Html.ActionLink("Back to List", "Index")
</div>
现在,如果我们运行应用程序,我们可以深入查看给定用户由于其参与的所有组而获得的的所有权限
导航到用户查找有效权限链接
查看选定用户跨所有组的有效权限
关于授权管理和网站的一些思考
ASP.NET Identity 系统为我们提供了一个易于使用的抽象,处理了一个复杂的主题,并且是当今新闻的焦点(可以说“来自今日报纸头条”)。ASP.NET Identity 系统是保护您的网站和管理身份验证的一种选择,但它不是唯一的方法。ASP.NET 团队提供了多种站点安全选项,实际上,Visual Studio 在设置 MVC 项目时提供了多种选择。
Identity 系统似乎是面向公众的网站、与社交媒体提供商集成以及需要更简单权限管理需求的网站的不错选择。管理网站安全的其他选项包括 Active Directory 集成以及用于内网服务的 Windows 身份验证。
在这些选项中,Identity 系统最容易实现,并且已内置到 ASP.NET 和 MVC 随附的项目模板中。
正如本文反复提到的,**您的安全系统的粒度越细,您拥有的控制权就越大。然而,这种控制是以复杂性为代价的**。不仅仅是代码的复杂性,还有管理您创建的各种用户、组和权限集所带来的复杂性。
我保证您不希望随意地在应用程序代码中散布新的角色,然后试图以后再管理它们。提前规划好,并牢记您的业务域。在可管理性和最小权限原则之间取得平衡。
本文提出的示例解决方案提供了一个起点。但请记住,使用 [Authorize]
属性实现的硬编码安全保护很容易成为代码维护的噩梦,如果您过度使用。此外,添加创建/编辑“权限”(请记住,就 Identity 框架而言,它们实际上是“角色”)的功能会带来新的便利,但也带来了新的麻烦。
我建议仅限于应用程序开发人员创建、编辑和删除角色(为他们创建一个特殊的角色/组!),并且实际上只是为了在将角色添加到代码中的相应 [Authorize]
属性之后,将角色添加到数据库。
我非常想听取对此的反馈。无论是您一直在寻找的确切解决方案,还是您发现了我的推理中存在明显的、严重的缺陷。请随时发表评论,或通过“关于作者”侧边栏下的地址给我发送电子邮件。
另外,Reddit 上的一些评论者建议 ClaimsIdentity
和 ClaimsAuthorizationManager
类提供了有趣的可能。我将调查这些类的潜力,并建议您也这样做。此外,Identity 2.0 的预览版本还增加了许多有趣的功能,值得关注。
其他资源和感兴趣的项目
- 第一部分:应用程序建模
- Github 上的源代码
- ASP.NET MVC 5 Identity:扩展和修改角色
- 扩展 ASP.NET MVC 5 中的身份账户并实现基于角色的认证
- C#:使用 DocX 以编程方式创建和操作 Word 文档
- 使用跨平台/OSS ExcelDataReader 读取 Excel 文件,无需依赖 Office 或 ACE