目录
1. 介绍
1.1 nopcommerce介绍
nopcommerce是国外的一个高质量的开源b2c网站系统,基于EntityFramework4.0和MVC3.0,使用Razor模板引擎,有很强的插件机制,包括支付配送功能都是通过插件来实现的.
nopcommerce主要模块从上往下Nop.Web、Nop.Admin、Nop.Web.Framework、Nop插件、Nop.Services、Nop.Core、Nop.Data。引用的第三方模块EntityFramework,Autofac(控制反转,即依赖注入),telerik.extern.mvc(后台管理用的界面,2.0后开始使用)
1.2 文章来由
我之前对ASP.NET MVC4的用户验证和权限管理这块内容理解的不深刻,一次偶然的机会使得我下载到了nopcommerce的源代码,该项目中含有访问控制功能,其功能页面如图1-1所示。
为了进一步理解MVC4的用户验证和权限管理的内容,我用了两天时间窥视了nopcommerce的访问控制功能,略有心得体会,并以我自己的理解内容写了一个Demo:AclDemo,该项目将在下一篇分析以及提供源代码。
图1-1
2. UML
该小节主要提供了nopcommerce项目中的访问控制功能的UML类图以及类图说明,在制作UML类图的过程中,我简化了UML类图,这样是为了让UML内容不冗余。简化后的UML类图只显示了和访问控制功能有关的内容。
2.1 实体类UML图
2.2 业务相关UML图
3. 核心代码分析
3.1 实体类源代码
/// <summary> /// Represents a customer role /// </summary> public partial class CustomerRole : BaseEntity { private ICollection<PermissionRecord> _permissionRecords; /// <summary> /// Gets or sets the customer role name /// </summary> public string Name { get; set; } /// <summary> /// Gets or sets a value indicating whether the customer role is active /// </summary> public bool Active { get; set; } /// <summary> /// Gets or sets a value indicating whether the customer role is system /// </summary> public bool IsSystemRole { get; set; } /// <summary> /// Gets or sets the customer role system name /// </summary> public string SystemName { get; set; } /// <summary> /// Gets or sets the permission records /// </summary> public virtual ICollection<PermissionRecord> PermissionRecords { get { return _permissionRecords ?? (_permissionRecords = new List<PermissionRecord>()); } protected set { _permissionRecords = value; } } }
CustomerRole类
/// <summary> /// Represents a permission record /// </summary> public partial class PermissionRecord : BaseEntity { private ICollection<CustomerRole> _customerRoles; /// <summary> /// Gets or sets the permission name /// </summary> public string Name { get; set; } /// <summary> /// Gets or sets the permission system name /// </summary> public string SystemName { get; set; } /// <summary> /// Gets or sets the permission category /// </summary> public string Category { get; set; } /// <summary> /// Gets or sets discount usage history /// </summary> public virtual ICollection<CustomerRole> CustomerRoles { get { return _customerRoles ?? (_customerRoles = new List<CustomerRole>()); } protected set { _customerRoles = value; } } }
PermissionRecord类
3.2 业务相关源代码
/// <summary> /// Customer service interface /// </summary> public partial interface ICustomerService { #region Customers /// <summary> /// Get customer by username /// </summary> /// <param name="username">Username</param> /// <returns>Customer</returns> Customer GetCustomerByUsername(string username); #endregion }
ICustomerService接口
/// <summary> /// Customer service /// </summary> public partial class CustomerService : ICustomerService { #region Constants #endregion #region Fields private readonly IRepository<Customer> _customerRepository; private readonly IRepository<CustomerRole> _customerRoleRepository; #endregion #region Ctor public CustomerService( IRepository<Customer> customerRepository, IRepository<CustomerRole> customerRoleRepository,) { this._customerRepository = customerRepository; this._customerRoleRepository = customerRoleRepository; } #endregion #region Methods #region Customers /// <summary> /// Get customer by username /// </summary> /// <param name="username">Username</param> /// <returns>Customer</returns> public virtual Customer GetCustomerByUsername(string username) { if (string.IsNullOrWhiteSpace(username)) return null; var query = from c in _customerRepository.Table orderby c.Id where c.Username == username select c; var customer = query.FirstOrDefault(); return customer; } #endregion #endregion }
CustomerService类
public partial interface IAuthenticationService { void SignIn(Customer customer, bool createPersistentCookie); void SignOut(); Customer GetAuthenticatedCustomer(); }
IAuthenticationService接口
public partial class FormsAuthenticationService : IAuthenticationService { private readonly HttpContextBase _httpContext; private readonly ICustomerService _customerService; private readonly CustomerSettings _customerSettings; private readonly TimeSpan _expirationTimeSpan; private Customer _cachedCustomer; /// <summary> /// Ctor /// </summary> /// <param name="httpContext">HTTP context</param> /// <param name="customerService">Customer service</param> /// <param name="customerSettings">Customer settings</param> public FormsAuthenticationService(HttpContextBase httpContext, ICustomerService customerService, CustomerSettings customerSettings) { this._httpContext = httpContext; this._customerService = customerService; this._customerSettings = customerSettings; this._expirationTimeSpan = FormsAuthentication.Timeout; } public virtual void SignIn(Customer customer, bool createPersistentCookie) { var now = DateTime.UtcNow.ToLocalTime(); var ticket = new FormsAuthenticationTicket( 1 /*version*/, _customerSettings.UsernamesEnabled ? customer.Username : customer.Email, now, now.Add(_expirationTimeSpan), createPersistentCookie, _customerSettings.UsernamesEnabled ? customer.Username : customer.Email, FormsAuthentication.FormsCookiePath); var encryptedTicket = FormsAuthentication.Encrypt(ticket); var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket); cookie.HttpOnly = true; if (ticket.IsPersistent) { cookie.Expires = ticket.Expiration; } cookie.Secure = FormsAuthentication.RequireSSL; cookie.Path = FormsAuthentication.FormsCookiePath; if (FormsAuthentication.CookieDomain != null) { cookie.Domain = FormsAuthentication.CookieDomain; } _httpContext.Response.Cookies.Add(cookie); _cachedCustomer = customer; } public virtual void SignOut() { _cachedCustomer = null; FormsAuthentication.SignOut(); } public virtual Customer GetAuthenticatedCustomer() { if (_cachedCustomer != null) return _cachedCustomer; if (_httpContext == null || _httpContext.Request == null || !_httpContext.Request.IsAuthenticated || !(_httpContext.User.Identity is FormsIdentity)) { return null; } var formsIdentity = (FormsIdentity)_httpContext.User.Identity; var customer = GetAuthenticatedCustomerFromTicket(formsIdentity.Ticket); if (customer != null && customer.Active && !customer.Deleted && customer.IsRegistered()) _cachedCustomer = customer; return _cachedCustomer; } public virtual Customer GetAuthenticatedCustomerFromTicket(FormsAuthenticationTicket ticket) { if (ticket == null) throw new ArgumentNullException("ticket"); var usernameOrEmail = ticket.UserData; if (String.IsNullOrWhiteSpace(usernameOrEmail)) return null; var customer = _customerSettings.UsernamesEnabled ? _customerService.GetCustomerByUsername(usernameOrEmail) : _customerService.GetCustomerByEmail(usernameOrEmail); return customer; } }
FormsAuthenticationService类
FormsAuthenticationService类实现代码分析
SignOut方法比较简单,主要是调用了FormsAuthentication的退出方法。重点介绍SignIn()和GetAuthenticatedCustomer()方法
在SignIn()方法中,首先创建一个类型为FormsAuthenticationTicket的ticket,并且将该ticket进行加密,然后将加密后的信息保存到cookie。
在GetAuthenticatedCustomer()中,如果说已经缓存了当前用户,则直接返回,如果说当前http上下文没有使用Forms验证的话或者验证不存在的话,则直接返回空置。紧接着,获取之前已经保存的ticket,取到ticket之后,根据保存的UserData,获取到已经登录用户的信息。
public enum CustomerLoginResults { /// <summary> /// Login successful /// </summary> Successful = 1, /// <summary> /// Customer dies not exist (email or username) /// </summary> CustomerNotExist = 2, /// <summary> /// Wrong password /// </summary> WrongPassword = 3, /// <summary> /// Account have not been activated /// </summary> NotActive = 4, /// <summary> /// Customer has been deleted /// </summary> Deleted = 5, /// <summary> /// Customer not registered /// </summary> NotRegistered = 6, }
CustomerLoginResults枚举类型
public partial interface ICustomerRegistrationService { /// <summary> /// Validate customer /// </summary> /// <param name="usernameOrEmail">Username or email</param> /// <param name="password">Password</param> /// <returns>Result</returns> CustomerLoginResults ValidateCustomer(string usernameOrEmail, string password); }
ICustomerRegistrationService接口
public partial class CustomerRegistrationService : ICustomerRegistrationService { #region Fields private readonly ICustomerService _customerService; private readonly CustomerSettings _customerSettings; #endregion #region Ctor /// <summary> /// Ctor /// </summary> /// <param name="customerService">Customer service</param> /// <param name="encryptionService">Encryption service</param> /// <param name="newsLetterSubscriptionService">Newsletter subscription service</param> /// <param name="localizationService">Localization service</param> /// <param name="storeService">Store service</param> /// <param name="rewardPointsSettings">Reward points settings</param> /// <param name="customerSettings">Customer settings</param> public CustomerRegistrationService(ICustomerService customerService, CustomerSettings customerSettings) { this._customerService = customerService; this._customerSettings = customerSettings; } #endregion #region Methods /// <summary> /// Validate customer /// </summary> /// <param name="usernameOrEmail">Username or email</param> /// <param name="password">Password</param> /// <returns>Result</returns> public virtual CustomerLoginResults ValidateCustomer(string usernameOrEmail, string password) { Customer customer; if (_customerSettings.UsernamesEnabled) customer = _customerService.GetCustomerByUsername(usernameOrEmail); else customer = _customerService.GetCustomerByEmail(usernameOrEmail); if (customer == null) return CustomerLoginResults.CustomerNotExist; string pwd = ""; bool isValid = pwd == customer.Password; if (!isValid) return CustomerLoginResults.WrongPassword; //save last login date customer.LastLoginDateUtc = DateTime.UtcNow; _customerService.UpdateCustomer(customer); return CustomerLoginResults.Successful; } #endregion }
CustomerRegistrationService
CustomerRegistrationService类实现代码分析
在验证用户的方法中还是比较简单的,首先根据用户用户设置,是用户名登录还是邮箱登录,然后根据用户名或者邮箱获取到用户信息,如果用户信息为空的话,则表示用户不存在,否则,则更新登录用户的用户信息。
nopcommerce项目中实现的用户验证绝非如此简单,它提供了密码加密格式的验证,感兴趣的同学可以下载看一看。
/// <summary> /// Work context /// </summary> public interface IWorkContext { /// <summary> /// Gets or sets the current customer /// </summary> Customer CurrentCustomer { get; set; } /// <summary> /// Get or set value indicating whether we're in admin area /// </summary> bool IsAdmin { get; set; } }
IWorkContext接口:该接口主要是提供当前工作环境的信息,比如当前用户信息、当前语言等等,简化版中只提供了当前用户信息
public partial class WebWorkContext : IWorkContext { #region Const private const string CustomerCookieName = "Nop.customer"; #endregion #region Fields private readonly HttpContextBase _httpContext; private readonly ICustomerService _customerService; private readonly IAuthenticationService _authenticationService; private Customer _cachedCustomer; #endregion #region Ctor public WebWorkContext(HttpContextBase httpContext, ICustomerService customerService, IAuthenticationService authenticationService,) { this._httpContext = httpContext; this._customerService = customerService; this._authenticationService = authenticationService; } #endregion #region Utilities protected virtual HttpCookie GetCustomerCookie() { if (_httpContext == null || _httpContext.Request == null) return null; return _httpContext.Request.Cookies[CustomerCookieName]; } protected virtual void SetCustomerCookie(Guid customerGuid) { if (_httpContext != null && _httpContext.Response != null) { var cookie = new HttpCookie(CustomerCookieName); cookie.HttpOnly = true; cookie.Value = customerGuid.ToString(); if (customerGuid == Guid.Empty) { cookie.Expires = DateTime.Now.AddMonths(-1); } else { int cookieExpires = 24*365; //TODO make configurable cookie.Expires = DateTime.Now.AddHours(cookieExpires); } _httpContext.Response.Cookies.Remove(CustomerCookieName); _httpContext.Response.Cookies.Add(cookie); } } #endregion #region Properties /// <summary> /// Gets or sets the current customer /// </summary> public virtual Customer CurrentCustomer { get { if (_cachedCustomer != null) return _cachedCustomer; Customer customer = null; //registered user if (customer == null || customer.Deleted || !customer.Active) { customer = _authenticationService.GetAuthenticatedCustomer(); } //impersonate user if required (currently used for 'phone order' support) if (customer != null && !customer.Deleted && customer.Active) { var impersonatedCustomerId = customer.GetAttribute<int?>(SystemCustomerAttributeNames.ImpersonatedCustomerId); if (impersonatedCustomerId.HasValue && impersonatedCustomerId.Value > 0) { var impersonatedCustomer = _customerService.GetCustomerById(impersonatedCustomerId.Value); if (impersonatedCustomer != null && !impersonatedCustomer.Deleted && impersonatedCustomer.Active) { //set impersonated customer _originalCustomerIfImpersonated = customer; customer = impersonatedCustomer; } } } //load guest customer if (customer == null || customer.Deleted || !customer.Active) { var customerCookie = GetCustomerCookie(); if (customerCookie != null && !String.IsNullOrEmpty(customerCookie.Value)) { Guid customerGuid; if (Guid.TryParse(customerCookie.Value, out customerGuid)) { var customerByCookie = _customerService.GetCustomerByGuid(customerGuid); if (customerByCookie != null && //this customer (from cookie) should not be registered !customerByCookie.IsRegistered()) customer = customerByCookie; } } } //create guest if not exists if (customer == null || customer.Deleted || !customer.Active) { customer = _customerService.InsertGuestCustomer(); } //validation if (!customer.Deleted && customer.Active) { SetCustomerCookie(customer.CustomerGuid); _cachedCustomer = customer; } return _cachedCustomer; } set { SetCustomerCookie(value.CustomerGuid); _cachedCustomer = value; } } /// <summary> /// Get or set value indicating whether we're in admin area /// </summary> public virtual bool IsAdmin { get; set; } #endregion }
WebWorkContext类:该类实现了IWorkContext接口,提供了web下的工作环境
WebWorkContext类实现代码分析
该类的CurrentCustomer属性的实现简化了一部分,我们首先判断用户信息是否已经缓存,如果已经缓存了,则直接返回,如果没有,则需要从FormsAuthenticationService中重新获取用户信息,根据该用户的信息,设置单个用户的cookie。更加详细的实现内容请下载nopcommerce源码。
/// <summary> /// Authorize permission /// </summary> /// <param name="permission">Permission record</param> /// <returns>true - authorized; otherwise, false</returns> bool Authorize(PermissionRecord permission); /// <summary> /// Authorize permission /// </summary> /// <param name="permission">Permission record</param> /// <param name="customer">Customer</param> /// <returns>true - authorized; otherwise, false</returns> bool Authorize(PermissionRecord permission, Customer customer); /// <summary> /// Authorize permission /// </summary> /// <param name="permissionRecordSystemName">Permission record system name</param> /// <returns>true - authorized; otherwise, false</returns> bool Authorize(string permissionRecordSystemName); /// <summary> /// Authorize permission /// </summary> /// <param name="permissionRecordSystemName">Permission record system name</param> /// <param name="customer">Customer</param> /// <returns>true - authorized; otherwise, false</returns> bool Authorize(string permissionRecordSystemName, Customer customer);
IPermissionService接口
public partial class PermissionService : IPermissionService { #region Constants /// <summary> /// Key pattern to clear cache /// </summary> private const string PERMISSIONS_PATTERN_KEY = "Nop.permission."; #endregion #region Fields private readonly IRepository<PermissionRecord> _permissionRecordRepository; private readonly ICustomerService _customerService; private readonly IWorkContext _workContext; #endregion #region Ctor /// <summary> /// Ctor /// </summary> /// <param name="permissionRecordRepository">Permission repository</param> /// <param name="customerService">Customer service</param> /// <param name="workContext">Work context</param> /// <param name="localizationService">Localization service</param> /// <param name="languageService">Language service</param> /// <param name="cacheManager">Cache manager</param> public PermissionService(IRepository<PermissionRecord> permissionRecordRepository, ICustomerService customerService, IWorkContext workContext) { this._permissionRecordRepository = permissionRecordRepository; this._customerService = customerService; this._workContext = workContext; } #endregion #region Methods /// <summary> /// Authorize permission /// </summary> /// <param name="permission">Permission record</param> /// <returns>true - authorized; otherwise, false</returns> public virtual bool Authorize(PermissionRecord permission) { return Authorize(permission, _workContext.CurrentCustomer); } /// <summary> /// Authorize permission /// </summary> /// <param name="permission">Permission record</param> /// <param name="customer">Customer</param> /// <returns>true - authorized; otherwise, false</returns> public virtual bool Authorize(PermissionRecord permission, Customer customer) { if (permission == null) return false; if (customer == null) return false; //old implementation of Authorize method //var customerRoles = customer.CustomerRoles.Where(cr => cr.Active); //foreach (var role in customerRoles) // foreach (var permission1 in role.PermissionRecords) // if (permission1.SystemName.Equals(permission.SystemName, StringComparison.InvariantCultureIgnoreCase)) // return true; //return false; return Authorize(permission.SystemName, customer); } /// <summary> /// Authorize permission /// </summary> /// <param name="permissionRecordSystemName">Permission record system name</param> /// <returns>true - authorized; otherwise, false</returns> public virtual bool Authorize(string permissionRecordSystemName) { return Authorize(permissionRecordSystemName, _workContext.CurrentCustomer); } /// <summary> /// Authorize permission /// </summary> /// <param name="permissionRecordSystemName">Permission record system name</param> /// <param name="customer">Customer</param> /// <returns>true - authorized; otherwise, false</returns> public virtual bool Authorize(string permissionRecordSystemName, Customer customer) { if (String.IsNullOrEmpty(permissionRecordSystemName)) return false; var customerRoles = customer.CustomerRoles.Where(cr => cr.Active); foreach (var role in customerRoles) if (Authorize(permissionRecordSystemName, role)) //yes, we have such permission return true; //no permission found return false; } #endregion #region Utilities /// <summary> /// Authorize permission /// </summary> /// <param name="permissionRecordSystemName">Permission record system name</param> /// <param name="customerRole">Customer role</param> /// <returns>true - authorized; otherwise, false</returns> protected virtual bool Authorize(string permissionRecordSystemName, CustomerRole customerRole) { if (String.IsNullOrEmpty(permissionRecordSystemName)) return false; string key = string.Format(PERMISSIONS_ALLOWED_KEY, customerRole.Id, permissionRecordSystemName); return _cacheManager.Get(key, () => { foreach (var permission1 in customerRole.PermissionRecords) if (permission1.SystemName.Equals(permissionRecordSystemName, StringComparison.InvariantCultureIgnoreCase)) return true; return false; }); } #endregion }
PermissionService类实现代码分析
PermissionService类实现代码分析
分析Authorize代码可以知道,首先是要获取当前用户所隶属的所有用户组,然后根据权限名称和用户组判断是否具有某个模块的访问权限。
3.3 相关控制器源代码
PermissionController类:主要是两个登录的Action,通过一下代码可以看出,当用户登录成功之后,需要调用 _authenticationService的登录方法,通过该方法记录已经登录用户的ID
[NopHttpsRequirement(SslRequirement.Yes)] public ActionResult Login(bool? checkoutAsGuest) { var model = new LoginModel(); model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.CheckoutAsGuest = checkoutAsGuest.GetValueOrDefault(); model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage; return View(model); } [HttpPost] [CaptchaValidator] public ActionResult Login(LoginModel model, string returnUrl, bool captchaValid) { //validate CAPTCHA if (_captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage && !captchaValid) { ModelState.AddModelError("", _localizationService.GetResource("Common.WrongCaptcha")); } if (ModelState.IsValid) { if (_customerSettings.UsernamesEnabled && model.Username != null) { model.Username = model.Username.Trim(); } var loginResult = _customerRegistrationService.ValidateCustomer(_customerSettings.UsernamesEnabled ? model.Username : model.Email, model.Password); switch (loginResult) { case CustomerLoginResults.Successful: { var customer = _customerSettings.UsernamesEnabled ? _customerService.GetCustomerByUsername(model.Username) : _customerService.GetCustomerByEmail(model.Email); //migrate shopping cart _shoppingCartService.MigrateShoppingCart(_workContext.CurrentCustomer, customer, true); //sign in new customer _authenticationService.SignIn(customer, model.RememberMe); //activity log _customerActivityService.InsertActivity("PublicStore.Login", _localizationService.GetResource("ActivityLog.PublicStore.Login"), customer); if (String.IsNullOrEmpty(returnUrl) || !Url.IsLocalUrl(returnUrl)) return RedirectToRoute("HomePage"); return Redirect(returnUrl); } case CustomerLoginResults.CustomerNotExist: ModelState.AddModelError("", _localizationService.GetResource("Account.Login.WrongCredentials.CustomerNotExist")); break; case CustomerLoginResults.Deleted: ModelState.AddModelError("", _localizationService.GetResource("Account.Login.WrongCredentials.Deleted")); break; case CustomerLoginResults.NotActive: ModelState.AddModelError("", _localizationService.GetResource("Account.Login.WrongCredentials.NotActive")); break; case CustomerLoginResults.NotRegistered: ModelState.AddModelError("", _localizationService.GetResource("Account.Login.WrongCredentials.NotRegistered")); break; case CustomerLoginResults.WrongPassword: default: ModelState.AddModelError("", _localizationService.GetResource("Account.Login.WrongCredentials")); break; } } //If we got this far, something failed, redisplay form model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.DisplayCaptcha = _captchaSettings.Enabled && _captchaSettings.ShowOnLoginPage; return View(model); }
SecurityController类:安全控制器,当用户权限不足时,则展示AccessDenied界面。Permissions是访问控制界面的Action
/// <summary>
/// 拒绝访问
/// </summary>
/// <param name="pageUrl"></param>
/// <returns></returns>
public ActionResult AccessDenied(string pageUrl)
{
var currentCustomer = _workContext.CurrentCustomer;
if (currentCustomer == null || currentCustomer.IsGuest())
{
_logger.Information(string.Format("Access denied to anonymous request on {0}", pageUrl));
return View();
} _logger.Information(string.Format("Access denied to user #{0} '{1}' on {2}", currentCustomer.Email, currentCustomer.Email, pageUrl)); return View();
} /// <summary>
/// GET:/Admin/Security/Permissions
/// 获取权限列表
/// </summary>
/// <returns></returns>
public ActionResult Permissions()
{
if (!_permissionService.Authorize(StandardPermissionProvider.ManageAcl))
return AccessDeniedView(); var model = new PermissionMappingModel(); var permissionRecords = _permissionService.GetAllPermissionRecords();
var customerRoles = _customerService.GetAllCustomerRoles(true);
foreach (var pr in permissionRecords)
{
model.AvailablePermissions.Add(new PermissionRecordModel
{
//Name = pr.Name,
Name = pr.GetLocalizedPermissionName(_localizationService, _workContext),
SystemName = pr.SystemName
});
}
foreach (var cr in customerRoles)
{
model.AvailableCustomerRoles.Add(new CustomerRoleModel
{
Id = cr.Id,
Name = cr.Name
});
}
foreach (var pr in permissionRecords)
foreach (var cr in customerRoles)
{
bool allowed = pr.CustomerRoles.Count(x => x.Id == cr.Id) > ;
if (!model.Allowed.ContainsKey(pr.SystemName))
model.Allowed[pr.SystemName] = new Dictionary<int, bool>();
model.Allowed[pr.SystemName][cr.Id] = allowed;
} return View(model);
} /// <summary>
/// GET:/Admin/Security/Permissions
/// 提交访问权限
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
[HttpPost, ActionName("Permissions")]
public ActionResult PermissionsSave(FormCollection form)
{
if (!_permissionService.Authorize(StandardPermissionProvider.ManageAcl))
return AccessDeniedView(); var permissionRecords = _permissionService.GetAllPermissionRecords();
var customerRoles = _customerService.GetAllCustomerRoles(true); foreach (var cr in customerRoles)
{
string formKey = "allow_" + cr.Id;
var permissionRecordSystemNamesToRestrict = form[formKey] != null ? form[formKey].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>(); foreach (var pr in permissionRecords)
{ bool allow = permissionRecordSystemNamesToRestrict.Contains(pr.SystemName);
if (allow)
{
if (pr.CustomerRoles.FirstOrDefault(x => x.Id == cr.Id) == null)
{
pr.CustomerRoles.Add(cr);
_permissionService.UpdatePermissionRecord(pr);
}
}
else
{
if (pr.CustomerRoles.FirstOrDefault(x => x.Id == cr.Id) != null)
{
pr.CustomerRoles.Remove(cr);
_permissionService.UpdatePermissionRecord(pr);
}
}
}
} SuccessNotification(_localizationService.GetResource("Admin.Configuration.ACL.Updated"));
return RedirectToAction("Permissions");
}
3.4 相关View源代码
PermissionMappingModel:访问控制管理界面模型
public partial class PermissionMappingModel : BaseNopModel { public PermissionMappingModel() { AvailablePermissions = new List<PermissionRecordModel>(); AvailableCustomerRoles = new List<CustomerRoleModel>(); Allowed = new Dictionary<string, IDictionary<int, bool>>(); } public IList<PermissionRecordModel> AvailablePermissions { get; set; } public IList<CustomerRoleModel> AvailableCustomerRoles { get; set; } //[permission system name] / [customer role id] / [allowed] public IDictionary<string, IDictionary<int, bool>> Allowed { get; set; } }
Permissions.cshtml:访问控制管理界面
@model PermissionMappingModel
@{ //page title ViewBag.Title = T("Admin.Configuration.ACL").Text;
}
@using (Html.BeginForm())
{ @Html.AntiForgeryToken() <div class="section-header"> <div class="title"> <img src="@Url.Content("~/Administration/Content/images/ico-configuration.png")" alt="" /> @T("Admin.Configuration.ACL") </div> <div class="options"> <input type="submit" name="save" class="k-button" value="@T("Admin.Common.Save")" /> </div> </div> <table class="adminContent"> <tr> <td> @if (Model.AvailablePermissions.Count == 0) { <text>No permissions defined</text> } else if (Model.AvailableCustomerRoles.Count == 0) { <text>No customer roles available</text> } else { <script type="text/javascript"> $(document).ready(function () { @foreach (var cr in Model.AvailableCustomerRoles) { <text> $('#selectall-@(cr.Id)').click(function () { $('.allow_@(cr.Id)').attr('checked', $(this).is(':checked')).change(); }); </text> } }); </script> <table class="tablestyle" cellspacing="0" rules="all" border="1" style="width: 100%; border-collapse: collapse;"> <tbody> <tr class="headerstyle"> <th scope="col"> <strong>@T("Admin.Configuration.ACL.Permission")</strong> </th> @foreach (var cr in Model.AvailableCustomerRoles) { <th scope="col"> <strong>@cr.Name</strong> <input type="checkbox" id="selectall-@(cr.Id)" /> </th> } </tr> @{ bool altRow = true; } @foreach (var pr in Model.AvailablePermissions) { altRow = !altRow; <tr class="@(altRow ? "altrowstyle" : "rowstyle")"> <td> <span>@pr.Name</span> </td> @foreach (var cr in Model.AvailableCustomerRoles) { var allowed = Model.Allowed.ContainsKey(pr.SystemName) && Model.Allowed[pr.SystemName][cr.Id]; <td> <input class="allow_@(cr.Id)" class="allow_@(cr.Id)" type="checkbox" value="@(pr.SystemName)" name="allow_@(cr.Id)" @(allowed ? " checked=checked" : null) /> </td> } </tr> } </tbody> </table> } </td> </tr> </table> }
4. 总结
通过对nopcommerce项目中的访问控制代码的分析,使得我对MVC4下的用户验证的实现有了进一步的了解:通过FormsAuthentication和HttpCookie来进行用户验证以及具体的实现过程。
该项目中的访问控制也是值得学习的,使得权限记录和用户组发送多对多的关系,当能查询到某个用户组含有对应的访问权限,则表示该用户组对这个模块有访问权限。
通过学习nopcommerce项目代码,我也编写了一个小Demo:AclDemo,该项目将在下一篇进行分析和源代码下载,敬请期待,感兴趣的可以关注我。