NopCommerce源代码分析之用户验证和权限管理
目录
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 实体类源代码
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; } } }
1 /// <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; } } }
3.2 业务相关源代码
1 /// <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 }
2
1 /// <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 }
1 public partial interface IAuthenticationService { void SignIn(Customer customer, bool createPersistentCookie); void SignOut(); Customer GetAuthenticatedCustomer(); }
1 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类实现代码分析
SignOut方法比较简单,主要是调用了FormsAuthentication的退出方法。重点介绍SignIn()和GetAuthenticatedCustomer()方法
在SignIn()方法中,首先创建一个类型为FormsAuthenticationTicket的ticket,并且将该ticket进行加密,然后将加密后的信息保存到cookie。
在GetAuthenticatedCustomer()中,如果说已经缓存了当前用户,则直接返回,如果说当前http上下文没有使用Forms验证的话或者验证不存在的话,则直接返回空置。紧接着,获取之前已经保存的ticket,取到ticket之后,根据保存的UserData,获取到已经登录用户的信息。
1 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, }
1 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); }
1 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类实现代码分析
在验证用户的方法中还是比较简单的,首先根据用户用户设置,是用户名登录还是邮箱登录,然后根据用户名或者邮箱获取到用户信息,如果用户信息为空的话,则表示用户不存在,否则,则更新登录用户的用户信息。
nopcommerce项目中实现的用户验证绝非如此简单,它提供了密码加密格式的验证,感兴趣的同学可以下载看一看。
1 /// <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; } }
1 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类实现代码分析
该类的CurrentCustomer属性的实现简化了一部分,我们首先判断用户信息是否已经缓存,如果已经缓存了,则直接返回,如果没有,则需要从FormsAuthenticationService中重新获取用户信息,根据该用户的信息,设置单个用户的cookie。更加详细的实现内容请下载nopcommerce源码。
1 /// <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);
1 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类实现代码分析
分析Authorize代码可以知道,首先是要获取当前用户所隶属的所有用户组,然后根据权限名称和用户组判断是否具有某个模块的访问权限。
3.3 相关控制器源代码
PermissionController类:主要是两个登录的Action,通过一下代码可以看出,当用户登录成功之后,需要调用 _authenticationService的登录方法,通过该方法记录已经登录用户的ID
1 [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
1 /// <summary>
2 /// 拒绝访问
3 /// </summary>
4 /// <param name="pageUrl"></param>
5 /// <returns></returns>
6 public ActionResult AccessDenied(string pageUrl)
7 {
8 var currentCustomer = _workContext.CurrentCustomer;
9 if (currentCustomer == null || currentCustomer.IsGuest())
10 {
11 _logger.Information(string.Format("Access denied to anonymous request on {0}", pageUrl));
12 return View();
13 }
14
15 _logger.Information(string.Format("Access denied to user #{0} '{1}' on {2}", currentCustomer.Email, currentCustomer.Email, pageUrl));
16
17
18 return View();
19 }
20
21 /// <summary>
22 /// GET:/Admin/Security/Permissions
23 /// 获取权限列表
24 /// </summary>
25 /// <returns></returns>
26 public ActionResult Permissions()
27 {
28 if (!_permissionService.Authorize(StandardPermissionProvider.ManageAcl))
29 return AccessDeniedView();
30
31 var model = new PermissionMappingModel();
32
33 var permissionRecords = _permissionService.GetAllPermissionRecords();
34 var customerRoles = _customerService.GetAllCustomerRoles(true);
35 foreach (var pr in permissionRecords)
36 {
37 model.AvailablePermissions.Add(new PermissionRecordModel
38 {
39 //Name = pr.Name,
40 Name = pr.GetLocalizedPermissionName(_localizationService, _workContext),
41 SystemName = pr.SystemName
42 });
43 }
44 foreach (var cr in customerRoles)
45 {
46 model.AvailableCustomerRoles.Add(new CustomerRoleModel
47 {
48 Id = cr.Id,
49 Name = cr.Name
50 });
51 }
52 foreach (var pr in permissionRecords)
53 foreach (var cr in customerRoles)
54 {
55 bool allowed = pr.CustomerRoles.Count(x => x.Id == cr.Id) > 0;
56 if (!model.Allowed.ContainsKey(pr.SystemName))
57 model.Allowed[pr.SystemName] = new Dictionary<int, bool>();
58 model.Allowed[pr.SystemName][cr.Id] = allowed;
59 }
60
61 return View(model);
62 }
63
64 /// <summary>
65 /// GET:/Admin/Security/Permissions
66 /// 提交访问权限
67 /// </summary>
68 /// <param name="form"></param>
69 /// <returns></returns>
70 [HttpPost, ActionName("Permissions")]
71 public ActionResult PermissionsSave(FormCollection form)
72 {
73 if (!_permissionService.Authorize(StandardPermissionProvider.ManageAcl))
74 return AccessDeniedView();
75
76 var permissionRecords = _permissionService.GetAllPermissionRecords();
77 var customerRoles = _customerService.GetAllCustomerRoles(true);
78
79
80 foreach (var cr in customerRoles)
81 {
82 string formKey = "allow_" + cr.Id;
83 var permissionRecordSystemNamesToRestrict = form[formKey] != null ? form[formKey].Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List<string>();
84
85 foreach (var pr in permissionRecords)
86 {
87
88 bool allow = permissionRecordSystemNamesToRestrict.Contains(pr.SystemName);
89 if (allow)
90 {
91 if (pr.CustomerRoles.FirstOrDefault(x => x.Id == cr.Id) == null)
92 {
93 pr.CustomerRoles.Add(cr);
94 _permissionService.UpdatePermissionRecord(pr);
95 }
96 }
97 else
98 {
99 if (pr.CustomerRoles.FirstOrDefault(x => x.Id == cr.Id) != null)
100 {
101 pr.CustomerRoles.Remove(cr);
102 _permissionService.UpdatePermissionRecord(pr);
103 }
104 }
105 }
106 }
107
108 SuccessNotification(_localizationService.GetResource("Admin.Configuration.ACL.Updated"));
109 return RedirectToAction("Permissions");
110 }
3.4 相关View源代码
PermissionMappingModel:访问控制管理界面模型
1 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:访问控制管理界面
1 @model PermissionMappingModel
2 @{ //page title ViewBag.Title = T("Admin.Configuration.ACL").Text;
3 }
4 @using (Html.BeginForm())
5 { @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>
6
7 }
4. 总结
通过对nopcommerce项目中的访问控制代码的分析,使得我对MVC4下的用户验证的实现有了进一步的了解:通过FormsAuthentication和HttpCookie来进行用户验证以及具体的实现过程。
该项目中的访问控制也是值得学习的,使得权限记录和用户组发送多对多的关系,当能查询到某个用户组含有对应的访问权限,则表示该用户组对这个模块有访问权限。
通过学习nopcommerce项目代码,我也编写了一个小Demo:AclDemo,该项目将在下一篇进行分析和源代码下载,敬请期待,感兴趣的可以关注我。