在MVC 3中,我试图弄清楚如何将基于角色和用户的安全性正确应用于我的一个视图(并遵循最佳实践)。我有一个视图,列出了任何给定"成员"(又名用户)的详细信息。我希望所有成员都能够查看任何其他成员的详细信息,但如果该成员正在查看自己的详细信息时,我希望他们能够编辑自己的详细内容。此外,网站管理员应该能够编辑任何成员的详细信息。
供您参考,我的网站配置如下:
- 我创建了自己的身份验证提供程序(而不是使用或重写SqlMembershipProvider),它非常简单,使用表单身份验证
- 我重写了默认的RoleProvider来实现我自己的IsUserInRole和GetRolesForUser方法。这两种方法都很有效
- 我的所有视图都使用从我的具体实体映射而来的"扁平"视图模型
- "我的详细信息"视图用于显示信息和编辑信息。使用一个标志(称为IsInEditMode)(并将其传递到自定义HtmlHelper中)来确定是简单地显示数据还是提供编辑器字段。我想保留这一观点
以下是我的具体要求。我在这个问题中列出了所有这些问题,因为我相信正确的答案将/应该解决所有这些问题(如果这是不正确的,请告诉我):
- 成员应该能够查看任何其他成员的详细信息
- 成员应该能够编辑他们自己的大部分详细信息,但他们详细信息的某些方面只能由网站管理员编辑(例如批准或临时的状态)
- 属于"站点管理员"角色的成员可以编辑所有内容
- 详细信息视图中的"编辑"按钮只需要根据成员的角色(作为站点管理员)或用户id/name与其登录凭据的验证来显示
- 控制器HttpPost Edit操作需要验证用户是否被批准进行编辑。我知道我可以用
[Authorize(Roles = "Site Administrator")]
这样的角色来装饰动作,但我不知道如何实现OR UserBeingEdited == LoggedInUser
属性 - 此外,我希望防止恶意编辑,例如用户"篡改"返回的用户ID值,以及XSS和CSRF攻击
以下是我适用于控制器和视图的代码(没有应用任何安全性)。欢迎提出任何建议!
MemberController.cs编辑操作sippet:
[HttpPost]
[ValidateAntiForgeryToken]
//TODO: Find out how to allow only "Site Administrator" roles OR the logged in user to edit their own information
public ActionResult Edit(MemberDetailViewModel memberDetailViewModel)
{
if(ModelState.IsValid)
{
//TODO: Find out how to prevent member ID tampering (including XSS and CSRF attacks)
var updatedMember = _memberServices.Find(memberDetailViewModel.MemberId);
Mapper.Map(memberDetailViewModel, updatedMember);
_memberRepository.InsertOrUpdate(updatedMember);
return RedirectToAction("Detail", new {id = memberDetailViewModel.MemberId});
}
else
{
return View("Detail", _memberQueries.GetMemberDetailViewModel(memberDetailViewModel.MemberId, isInEditMode:true));
}
}
成员详细信息.chtml视图(仅包括相关部分):
@model MyApp.Web.Areas.Members.Models.MemberDetailViewModel
@using (Html.BeginForm("Edit", "Member", FormMethod.Post, new { id = "memberDetailForm", enctype = "multipart/form-data" }))
{
<fieldset id="pageTitle">
<h2>
<!-- TODO: Only allow editing by "Site Administrators" or the verified logged in user -->
@if (Model.IsInEditMode)
{
@:Editing @Model.FirstName @Model.LastName
<a class="button" href="@Url.Action("Detail", new { id = @Model.MemberId })">Cancel</a>
<input class="button" type="submit" value="Save" />
@Html.HiddenFor(x => Model.MemberId)
@Html.AntiForgeryToken()
}
else
{
@:Details for @Model.FirstName @Model.LastName
<a class="button" href="@Url.Action("Edit", new { id = @Model.MemberId })">Edit</a>
}
</h2>
</fieldset>
<fieldset>
<legend>Basic Information</legend>
<table>
<tr>
<td class="label">
First Name:
</td>
<td class="field">
@Html.DisplayOrEditorFor(x => Model.FirstName, Model.IsInEditMode)
@Html.ValidationMessageFor(x => Model.FirstName)
</td>
</tr>
<!-- Other properties omitted -->
<tr>
<td class="label">
Status:
</td>
<td class="field">
<!-- TODO: This status field should only be editable by "Site Administrator", not the user -->
@Html.DisplayOrEditorDropDownListFor(x => Model.StatusId, Model.StatusList, Model.IsInEditMode)
</td>
</tr>
</table>
</fieldset>
}
如果需要更好地理解我的视图,这里有一个DisplayOrEditorFor自定义HtmlHelper函数的示例:
public static MvcHtmlString DisplayOrEditorFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, bool isInEditMode)
{
return isInEditMode ? System.Web.Mvc.Html.EditorExtensions.EditorFor(htmlHelper, expression) : System.Web.Mvc.Html.DisplayExtensions.DisplayFor(htmlHelper, expression);
}
在您的情况下,最简单的解决方案是仅当编辑用户的id==当前用户的id,或者用户是管理员时才设置IsInEditMode。
您不想使用除通用Authorize属性之外的任何属性,因为所有注册的用户都可以访问该页面,因此您不希望或不需要对页面本身进行额外的授权,只需要对字段进行授权,并且这些字段由您的IsInEditMode布尔值控制。
您还可以在"编辑"链接上进行测试,看看它是当前用户还是管理员,以及对只有管理员才能编辑的字段进行特殊测试。
所以,你可以这样做:
@Html.DisplayOrEditorDropDownListFor(x => Model.StatusId, Model.StatusList,
Model.IsInEditMode && User.IsInRole("Administrator"))
是否处于编辑模式将在控制器中设置。只有当它是当前用户或管理员时,它才会允许EditMode设置为true。