目录
步骤4:授权验证“ClaimsAuthorizeAttribute”
- 从Github下载源代码
介绍
ASP.NET Identity是通过建立ASP.NET应用程序来对用户进行身份验证和授权的membershi系统。
服务器使用身份验证来确定谁正在访问其信息或网站。在身份验证中,用户或客户必须通过使用电子邮件和文字或使用各种社交提供程序登录来在Web服务器上证明其身份。
授权是一个过程,服务器通过该过程可以确定客户端在成功进行身份验证之后是否具有使用资源或访问文件的权限。
有关更多详细信息,请阅读原始文章。
ASP.NET身份仅使用角色和声明来实现授权,在某些应用程序中,如果您的业务逻辑需要额外的授权管理层(例如用户组),则您总是尝试从ASP.NET Identity上实现这一点,因为它们本身并不提供表格或方法来实现这一层。
背景
ASP.NET Identity本机提供了一个默认架构,您可以在其中添加任何扩展表作为以下架构:
使用代码
此实现包括以下步骤:
步骤1:数据库修改
当然,要引入用户组功能,您需要添加一些表来存储此信息。
在此实现中,我们只需要添加以下表格:
- tblGroups {PK_Id, Name}
- tblGroupActions {PK_Id, FK_Group, ActionName}
- tblUserGroups {PK_Id, FK_Group, FK_User}
但是tblActions呢在哪里?这就是技巧,将在下一节中说明。
步骤2:动作(Actions)储存
我的动作将存储在哪里?实际上,动作是在应用程序层中定义的,因此每次创建动作时,在某些表中添加动作条目都是多余的。
因此,如果我们创建了一个函数来检索应用程序中的所有动作,那么我们的工作就完成了。这可以通过以下功能实现:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
namespace UserManagmentSystem.Controllers
{
public class ImplementedMethods
{
public List ActiveMethods;
public ImplementedMethods()
{
var asm = Assembly.GetExecutingAssembly();
ActiveMethods = asm.GetTypes()
.Where(type => typeof(Controller)
.IsAssignableFrom(type))
.SelectMany(type => type.GetMethods())
.Where(method => method.IsPublic
&& !method.IsDefined(typeof(NonActionAttribute))
&& (
method.CustomAttributes.Any
(s => s.AttributeType == typeof(HttpPostAttribute)) ||
method.CustomAttributes.Any
(s => s.AttributeType == typeof(HttpGetAttribute))
)
&& (
!method.CustomAttributes.Any(s => s.AttributeType ==
typeof(AllowAnonymousAttribute))
)
&& method.CustomAttributes.Any
(s => s.AttributeType == typeof(ClaimsAuthorizeAttribute))
&& (
method.ReturnType == typeof(ActionResult) ||
method.ReturnType == typeof(Task) ||
method.ReturnType == typeof(String)
)
)
.Select(m => m.CustomAttributes.FirstOrDefault
(s => s.AttributeType == typeof(HttpPostAttribute) ||
s.AttributeType == typeof(HttpGetAttribute)).
AttributeType.Name.Replace("Attribute", "") + " : " +
m.DeclaringType.ToString().Split('.')[2].Replace
("Controller", "") + "/" + m.Name).ToList();
}
}
}
先前功能的主要目标是检索所有控制器方法,这些方法是:
是由GET或POST装饰,而不是AllowAnonymousAttribute,并有一个自定义装饰“ClaimsAuthorizeAttribute”,以确保行动将被添加到池中,并且确保操作将检查登录用户的声明
步骤3:分组控制动作
现在,它很容易创建组到控制器{ Add,Edit,Delete,Details,AddAction,RevokeAction}:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using AccountingSystem.Models;
using Audit.Mvc;
namespace UserManagmentSystem.Controllers
{
[Authorize]
public class GroupsController : Controller
{
private AccountingdbEntities db = new AccountingdbEntities();
// GET: Groups
public ActionResult Index()
{
return View(db.tblGroups.ToList());
}
// GET: Groups/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
tblGroups tblGroups = db.tblGroups.Find(id);
if (tblGroups == null)
{
return HttpNotFound();
}
GroupsViewModel groupsViewModel = new GroupsViewModel();
ImplementedMethods implementedMethods = new ImplementedMethods();
groupsViewModel.Name = tblGroups.Name;
groupsViewModel.PK_Id = tblGroups.PK_Id;
groupsViewModel.Actions = tblGroups.tblGroupActions.Select
(s => new ActionsViewModel
{
PK_Id = s.PK_Id,
Name = s.ActionName
}).ToList();
groupsViewModel.Users = tblGroups.tblUserGroups.Select(s => new UsersViewModel
{
Id = s.AspNetUsers.Id,
Name = s.AspNetUsers.UserName,
OrdersConut = db.tblOrderStatusHistory.Where
(k => k.FK_User == s.AspNetUsers.Id && k.tblOrderStatus.IsFirst &&
k.tblOrderStatus.IsDefault).Count()
}).ToList();
ViewBag.AvailableActions = implementedMethods.ActiveMethods.Where
(t => !db.tblGroupActions.Any(k => k.FK_Group == id &&
k.ActionName == t)).Select(s => new SelectListItem
{ Value = s, Text = s }).ToList();
return View(groupsViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RevokeAction(int GroupId, string ActionName)
{
tblGroupActions tblGroupActions =
db.tblGroupActions.FirstOrDefault
(s=> s.FK_Group == GroupId && s.ActionName == ActionName);
if (tblGroupActions!= null)
{
db.tblGroupActions.Remove(tblGroupActions);
db.SaveChanges();
}
return RedirectToAction("Details", new { id = GroupId });
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddAction(int GroupId, string ActionName)
{
tblGroupActions tblGroupActions = new tblGroupActions()
{ ActionName = ActionName, FK_Group = GroupId };
if (tblGroupActions != null)
{
db.tblGroupActions.Add(tblGroupActions);
db.SaveChanges();
}
return RedirectToAction("Details", new { id = GroupId });
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult RevokeUser(int GroupId, string UserId)
{
tblUserGroups tblUserGroups = db.tblUserGroups.FirstOrDefault
(s => s.FK_Group == GroupId && s.FK_User == UserId);
if (tblUserGroups != null)
{
db.tblUserGroups.Remove(tblUserGroups);
db.SaveChanges();
}
return RedirectToAction("Details", new { id = GroupId });
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
步骤4:授权验证“ClaimsAuthorizeAttribute”
通过应用此自定义AuthorizeAttribute,我们可以确保应用程序将检查CurrentUser声明是否具有此操作的声明。
关于身份声明和角色的妙处在于,在当前登录会话中,UserManager将用户声明和角色存储在内存中,因此无需每次都访问数据库来检查声明:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
namespace UserManagmentSystem.Controllers
{
public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
private string claimType;
public ClaimsAuthorizeAttribute(string type)
{
this.claimType = type;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
var user = filterContext.HttpContext.User as ClaimsPrincipal;
if (user != null && user.HasClaim(claimType, claimType))
{
base.OnAuthorization(filterContext);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
namespace UserManagmentSystem.Controllers
{
[Authorize]
public class CarriersController : Controller
{
[ClaimsAuthorize("HttpGet : Carriers/Index")]
[HttpGet]
public ActionResult Index()
{
//
}
[ClaimsAuthorize("HttpGet : Carriers/Details")]
[HttpGet]
public ActionResult Details(int? id)
{
//
}
[ClaimsAuthorize("HttpGet : Carriers/Create")]
[HttpGet]
public ActionResult Create()
{
//
}
[HttpPost]
[ValidateAntiForgeryToken]
[ClaimsAuthorize("HttpPost : Carriers/Create")]
public ActionResult Create(Model)
{
//
}
}
}
第5步:声明分配
在此实现中,我们发现最好通过登录分配用户声明,因为tblGroupActions 表中的任何更改仅在登录后才会生效。
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = UserManager.Find(model.Email, model.Password);
if (user != null)
{
var UserClaim = user.Claims.AsEnumerable().ToList();
foreach (IdentityUserClaim claim in UserClaim)
{
await UserManager.RemoveClaimAsync
(user.Id, new Claim(claim.ClaimType, claim.ClaimValue));
}
var UserClaims = db.tblGroupActions.AsEnumerable().Where
(s => s.tblGroups.tblUserGroups.Any(l => l.FK_User == user.Id))
.Select(k => new Claim(k.ActionName, k.ActionName)).ToList();
foreach (Claim cliam in UserClaims)
{
await UserManager.AddClaimAsync(user.Id, cliam);
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout,
// change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync
(model.Email, model.Password, model.RememberMe, shouldLockout: true);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode",
new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
为特定组添加操作: