【源码笔记】BlogEngine.Net 中的权限管理

2020-12-13 05:20

阅读:249

标签:des   cPage   style   c   class   blog   

       BlogEngine.Net 是个功能点很全面的开源博客系统,容易安装和实现定制,开放接口支持TrackBack,可以定义主题配置数据源等等。可谓五脏俱全,这里先记录一下它基于Membership的权限管理(一般只说到角色就没了)。

      Membership是.net2.0的时候就出来了,现在的最新版本是Identity(微软已经将这个Asp.net项目开源 https://github.com/aspnet/Identity  )。权限管理就是处理用户、角色、和具体权限的关系。用户和角色是多对多的关系,角色和权限也是多对多的关系。 用户通过拥有角色来间接获得权限。但为什么要使用Membership呢,我们可以在数据库中建几张表就可以搞定这些关系了,因为想用Asp.Net自带的账户管理,比自己实现的要安全方便。废话不多说了,切入正题。

      

一、MembershipProvider 用户/账户管理

功能:用户注册,登陆,账户管理

       Membership是基于Provider实现,在Asp.Net中到处可以见到Provider的身影。MembershipProvider是一个抽象类,主要负责给Membership提供用户账户验证方面的方法。BlogEngine实现了XmlMembershipProvider和DbMembershipProvider。再通过Webconfig的配置来决定启用哪一种MembershipProvider。

1. 以XmlMembershipProvider为例,比较重要的一些方法是CreateUser,ValidateUser,ChangePassword 等。

(完整的源码可以去官网下载,这里不列出了) 

soscw.com,搜素材soscw.com,搜素材
public class XmlMembershipProvider : MembershipProvider
    {
       //....
      
         /// 
        /// Creates the user.
        /// 
        /// The username.
        /// The password.
        /// The email.
        /// The password question.
        /// The password answer.
        /// if set to true [approved].
        /// The provider user key.
        /// The status.
        /// A Membership User.
        public override MembershipUser CreateUser(
            string username, 
            string password, 
            string email, 
            string passwordQuestion, 
            string passwordAnswer, 
            bool approved, 
            object providerUserKey, 
            out MembershipCreateStatus status)
        {
            this.ReadMembershipDataStore();

            if (this.users[Blog.CurrentInstance.Id].ContainsKey(username))
            {
                throw new NotSupportedException("The username is already in use. Please choose another username.");
            }

            var doc = new XmlDocument();
            doc.Load(XmlFullyQualifiedPath);

            XmlNode xmlUserRoot = doc.CreateElement("User");
            XmlNode xmlUserName = doc.CreateElement("UserName");
            XmlNode xmlPassword = doc.CreateElement("Password");
            XmlNode xmlEmail = doc.CreateElement("Email");
            XmlNode xmlLastLoginTime = doc.CreateElement("LastLoginTime");

            xmlUserName.InnerText = username;

            string passwordPrep = this.passwordFormat == MembershipPasswordFormat.Hashed ? Utils.HashPassword(password) : password;

            xmlPassword.InnerText = passwordPrep;

            xmlEmail.InnerText = email;
            xmlLastLoginTime.InnerText = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);

            xmlUserRoot.AppendChild(xmlUserName);
            xmlUserRoot.AppendChild(xmlPassword);
            xmlUserRoot.AppendChild(xmlEmail);
            xmlUserRoot.AppendChild(xmlLastLoginTime);

            doc.SelectSingleNode("Users").AppendChild(xmlUserRoot);
            doc.Save(XmlFullyQualifiedPath);

            status = MembershipCreateStatus.Success;
            var user = new MembershipUser(
                this.Name, 
                username, 
                username, 
                email, 
                passwordQuestion, 
                passwordPrep, 
                approved, 
                false, 
                DateTime.Now, 
                DateTime.Now, 
                DateTime.Now, 
                DateTime.Now, 
                DateTime.MaxValue);
            this.users[Blog.CurrentInstance.Id].Add(username, user);
            return user;
        }

        /// 
        /// Removes a user from the membership data source.
        /// 
        /// The name of the user to delete.
        /// true to delete data related to the user from the database; false to leave data related to the user in the database.
        /// 
        /// true if the user was successfully deleted; otherwise, false.
        /// 
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            this.ReadMembershipDataStore();

            var doc = new XmlDocument();
            doc.Load(XmlFullyQualifiedPath);

            foreach (XmlNode node in
                doc.GetElementsByTagName("User").Cast().Where(node => node.ChildNodes[0].InnerText.Equals(username, StringComparison.OrdinalIgnoreCase)))
            {
                doc.SelectSingleNode("Users").RemoveChild(node);
                doc.Save(XmlFullyQualifiedPath);
                this.users[Blog.CurrentInstance.Id].Remove(username);
                return true;
            }

            return false;
        }
  /// 
        /// Processes a request to update the password for a membership user.
        /// 
        /// The user to update the password for.
        /// The current password for the specified user.
        /// The new password for the specified user.
        /// 
        /// true if the password was updated successfully; otherwise, false.
        /// 
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            var doc = new XmlDocument();
            doc.Load(XmlFullyQualifiedPath);
            var nodes = doc.GetElementsByTagName("User");
            foreach (XmlNode node in nodes)
            {
                if (!node["UserName"].InnerText.Equals(username, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                if (!this.CheckPassword(node["Password"].InnerText, oldPassword))
                {
                    continue;
                }
                
                string passwordPrep = this.passwordFormat == MembershipPasswordFormat.Hashed ? Utils.HashPassword(newPassword) : newPassword;

                node["Password"].InnerText = passwordPrep;
                doc.Save(XmlFullyQualifiedPath);

                this.users = null;
                this.ReadMembershipDataStore();
                return true;
            }

            return false;
        }
//......
   }
View Code

2.webconfig配置:

 在system.web目录下。通过defaultProvider来指定。

soscw.com,搜素材
 membership defaultProvider="XmlMembershipProvider">
      providers>
        clear />
        add name="XmlMembershipProvider" type="BlogEngine.Core.Providers.XmlMembershipProvider, BlogEngine.Core" description="XML membership provider" passwordFormat="Hashed" />
        add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="BlogEngine" applicationName="BlogEngine" />
        add name="DbMembershipProvider" type="BlogEngine.Core.Providers.DbMembershipProvider, BlogEngine.Core" passwordFormat="Hashed" connectionStringName="BlogEngine" />
      providers>
    membership>
soscw.com,搜素材

 这里看到的SqlMembershipProvider是在.net2.0中就自带的一个Provider。

 3.那这样就可以在我们的AccountController中调用了。

soscw.com,搜素材
[HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // 尝试注册用户
                try
                {
                    Membership.CreateUser(model.UserName, model.Password, model.Email);
                    FormsAuthentication.SetAuthCookie(model.UserName, false);
                    return RedirectToAction("Index", "Home");
                }
                catch (MembershipCreateUserException e)
                {
                    ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                }
            }
            // 如果我们进行到这一步时某个地方出错,则重新显示表单
            return View(model);
        }
soscw.com,搜素材

另外还封装了一个UsersRepository,并通过API的方式供外部使用。

soscw.com,搜素材soscw.com,搜素材
 public class UsersRepository : IUsersRepository
    {
        /// 
        /// Post list
        /// 
        /// Filter expression
        /// Order expression
        /// Records to skip
        /// Records to take
        /// List of users
        public IEnumerable Find(int take = 10, int skip = 0, string filter = "", string order = "")
        {
            if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.AccessAdminPages))
                throw new System.UnauthorizedAccessException();

            var users = new List();
            int count;
            var userCollection = Membership.Provider.GetAllUsers(0, 999, out count);
            var members = userCollection.Cast().ToList();

            foreach (var m in members)
            {
                users.Add(new BlogUser { 
                    IsChecked = false, 
                    UserName = m.UserName, 
                    Email = m.Email,
                    Profile = GetProfile(m.UserName),
                    Roles = GetRoles(m.UserName)
                });
            }

            var query = users.AsQueryable().Where(filter);

            // if take passed in as 0, return all
            if (take == 0) take = users.Count;

            return query.OrderBy(order).Skip(skip).Take(take);
        }

        /// 
        /// Get single post
        /// 
        /// User id
        /// User object
        public BlogUser FindById(string id)
        {
            if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.AccessAdminPages))
                throw new System.UnauthorizedAccessException();

            var users = new List();
            int count;
            var userCollection = Membership.Provider.GetAllUsers(0, 999, out count);
            var members = userCollection.Cast().ToList();

            foreach (var m in members)
            {
                users.Add(new BlogUser
                {
                    IsChecked = false,
                    UserName = m.UserName,
                    Email = m.Email,
                    Profile = GetProfile(m.UserName),
                    Roles = GetRoles(m.UserName)
                });
            }
            return users.AsQueryable().Where("UserName.ToLower() == \"" + id.ToLower() + "\"").FirstOrDefault();
        }

        /// 
        /// Add new user
        /// 
        /// Blog user
        /// Saved user
        public BlogUser Add(BlogUser user)
        {
            if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.CreateNewUsers))
                throw new System.UnauthorizedAccessException();

            if (user == null || string.IsNullOrEmpty(user.UserName)
                || string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
            {
                throw new ApplicationException("Error adding new user; Missing required fields");
            }

            if (!Security.IsAuthorizedTo(Rights.CreateNewUsers))
                throw new ApplicationException("Not authorized");

            // create user
            var usr = Membership.CreateUser(user.UserName, user.Password, user.Email);
            if (usr == null)
                throw new ApplicationException("Error creating new user");

            UpdateUserProfile(user);

            UpdateUserRoles(user);

            user.Password = "";
            return user;
        }

        /// 
        /// Update user
        /// 
        /// User to update
        /// True on success
        public bool Update(BlogUser user)
        {
            if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.EditOwnUser))
                throw new System.UnauthorizedAccessException();

            if (user == null || string.IsNullOrEmpty(user.UserName) || string.IsNullOrEmpty(user.Email))
                throw new ApplicationException("Error adding new user; Missing required fields");

            if (!Security.IsAuthorizedTo(Rights.EditOwnUser))
                throw new ApplicationException("Not authorized");

            // update user
            var usr = Membership.GetUser(user.UserName);

            if (usr == null)
                return false;

            usr.Email = user.Email;
            Membership.UpdateUser(usr);

            UpdateUserProfile(user);

            UpdateUserRoles(user);

            return true;
        }

        /// 
        /// Save user profile
        /// 
        /// Blog user
        /// True on success
        public bool SaveProfile(BlogUser user)
        {
            return UpdateUserProfile(user);
        }

        /// 
        /// Delete user
        /// 
        /// User ID
        /// True on success
        public bool Remove(string id){
            if (string.IsNullOrEmpty(id))
                return false;

            if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.DeleteUserSelf))
                throw new System.UnauthorizedAccessException();

            bool isSelf = id.Equals(Security.CurrentUser.Identity.Name, StringComparison.OrdinalIgnoreCase);

            if (isSelf && !Security.IsAuthorizedTo(Rights.DeleteUserSelf))
                throw new ApplicationException("Not authorized");

            else if (!isSelf && !Security.IsAuthorizedTo(Rights.DeleteUsersOtherThanSelf))
                throw new ApplicationException("Not authorized");

            // Last check - it should not be possible to remove the last use who has the right to Add and/or Edit other user accounts. If only one of such a 
            // user remains, that user must be the current user, and can not be deleted, as it would lock the user out of the BE environment, left to fix
            // it in XML or SQL files / commands. See issue 11990
            bool adminsExist = false;
            MembershipUserCollection users = Membership.GetAllUsers();
            foreach (MembershipUser user in users)
            {
                string[] roles = Roles.GetRolesForUser(user.UserName);

                // look for admins other than ‘id‘ 
                if (!id.Equals(user.UserName, StringComparison.OrdinalIgnoreCase) && (Right.HasRight(Rights.EditOtherUsers, roles) || Right.HasRight(Rights.CreateNewUsers, roles)))
                {
                    adminsExist = true;
                    break;
                }
            }

            if (!adminsExist)
                throw new ApplicationException("Can not delete last admin");

            string[] userRoles = Roles.GetRolesForUser(id);

            try
            {
                if (userRoles.Length > 0)
                {
                    Roles.RemoveUsersFromRoles(new string[] { id }, userRoles);
                }

                Membership.DeleteUser(id);

                var pf = AuthorProfile.GetProfile(id);
                if (pf != null)
                {
                    BlogEngine.Core.Providers.BlogService.DeleteProfile(pf);
                }
            }
            catch (Exception ex)
            {
                Utils.Log("Error deleting user", ex.Message);
                return false;
            }
            return true;
        }

        #region Private methods

        static Profile GetProfile(string id)
        {
            if (!Utils.StringIsNullOrWhitespace(id))
            {
                var pf = AuthorProfile.GetProfile(id);
                if (pf == null)
                {
                    pf = new AuthorProfile(id);
                    pf.Birthday = DateTime.Parse("01/01/1900");
                    pf.DisplayName = id;
                    pf.EmailAddress = Utils.GetUserEmail(id);
                    pf.FirstName = id;
                    pf.Private = true;
                    pf.Save();
                }
                
                return new Profile { 
                    AboutMe = string.IsNullOrEmpty(pf.AboutMe) ? "" : pf.AboutMe,
                    Birthday = pf.Birthday.ToShortDateString(),
                    CityTown = string.IsNullOrEmpty(pf.CityTown) ? "" : pf.CityTown,
                    Country = string.IsNullOrEmpty(pf.Country) ? "" : pf.Country,
                    DisplayName = pf.DisplayName,
                    EmailAddress = pf.EmailAddress,
                    PhoneFax = string.IsNullOrEmpty(pf.PhoneFax) ? "" : pf.PhoneFax,
                    FirstName = string.IsNullOrEmpty(pf.FirstName) ? "" : pf.FirstName,
                    Private = pf.Private,
                    LastName = string.IsNullOrEmpty(pf.LastName) ? "" : pf.LastName,
                    MiddleName = string.IsNullOrEmpty(pf.MiddleName) ? "" : pf.MiddleName,
                    PhoneMobile = string.IsNullOrEmpty(pf.PhoneMobile) ? "" : pf.PhoneMobile,
                    PhoneMain = string.IsNullOrEmpty(pf.PhoneMain) ? "" : pf.PhoneMain,
                    PhotoUrl = string.IsNullOrEmpty(pf.PhotoUrl) ? "" : pf.PhotoUrl.Replace("\"", ""),
                    RegionState = string.IsNullOrEmpty(pf.RegionState) ? "" : pf.RegionState
                };
            }
            return null;
        }

        static List GetRoles(string id)
        {
            var roles = new List();
            var userRoles = new List();

            roles.AddRange(System.Web.Security.Roles.GetAllRoles().Select(r => new Data.Models.RoleItem { RoleName = r, IsSystemRole = Security.IsSystemRole(r) }));
            roles.Sort((r1, r2) => string.Compare(r1.RoleName, r2.RoleName));

            foreach (var r in roles)
            {
                if (System.Web.Security.Roles.IsUserInRole(id, r.RoleName))
                {
                    userRoles.Add(r);
                }
            }
            return userRoles;
        }

        static bool UpdateUserProfile(BlogUser user)
        {
            if (user == null || string.IsNullOrEmpty(user.UserName))
                return false;

            var pf = AuthorProfile.GetProfile(user.UserName) 
                ?? new AuthorProfile(user.UserName);
            try
            {
                pf.DisplayName = user.Profile.DisplayName;
                pf.FirstName = user.Profile.FirstName;
                pf.MiddleName = user.Profile.MiddleName;
                pf.LastName = user.Profile.LastName;
                pf.EmailAddress = user.Email; // user.Profile.EmailAddress;

                DateTime date;
                if (user.Profile.Birthday.Length == 0)
                    user.Profile.Birthday = "1/1/1001";

                if (DateTime.TryParse(user.Profile.Birthday, out date))
                    pf.Birthday = date;

                pf.PhotoUrl = user.Profile.PhotoUrl.Replace("\"", "");
                pf.Private = user.Profile.Private;

                pf.PhoneMobile = user.Profile.PhoneMobile;
                pf.PhoneMain = user.Profile.PhoneMain;
                pf.PhoneFax = user.Profile.PhoneFax;

                pf.CityTown = user.Profile.CityTown;
                pf.RegionState = user.Profile.RegionState;
                pf.Country = user.Profile.Country;
                pf.AboutMe = user.Profile.AboutMe;

                pf.Save();
                UpdateProfileImage(pf);
            }
            catch (Exception ex)
            {
                Utils.Log("Error editing profile", ex);
                return false;
            }
            return true;
        }

        static bool UpdateUserRoles(BlogUser user)
        {
            try
            {
                // remove all user roles and add only checked
                string[] currentRoles = Roles.GetRolesForUser(user.UserName);
                if (currentRoles.Length > 0)
                    Roles.RemoveUserFromRoles(user.UserName, currentRoles);

                if (user.Roles.Count > 0)
                {
                    string[] roles = user.Roles.Where(ur => ur.IsChecked).Select(r => r.RoleName).ToArray();

                    if(roles.Length > 0)
                        Roles.AddUsersToRoles(new string[] { user.UserName }, roles);
                    else
                        Roles.AddUsersToRoles(new string[] { user.UserName }, new string[] { BlogConfig.AnonymousRole });
                }
                return true;
            }
            catch (Exception ex)
            {
                Utils.Log("Error updating user roles", ex);
                return false;
            }
        }

        /// 
        /// Remove any existing profile images
        /// 
        /// User profile
        static void UpdateProfileImage(AuthorProfile profile)
        {
            var dir = BlogEngine.Core.Providers.BlogService.GetDirectory("/avatars");

            if(string.IsNullOrEmpty(profile.PhotoUrl))
            {
                foreach (var f in dir.Files)
                {
                    var dot = f.Name.IndexOf(".");
                    var img = dot > 0 ? f.Name.Substring(0, dot) : f.Name;
                    if (profile.UserName == img)
                    {
                        f.Delete();
                    }
                }
            }
            else
            {
                foreach (var f in dir.Files)
                {
                    var dot = f.Name.IndexOf(".");
                    var img = dot > 0 ? f.Name.Substring(0, dot) : f.Name;
                    // delete old profile image saved with different name
                    // for example was admin.jpg and now admin.png
                    if (profile.UserName == img && f.Name != profile.PhotoUrl.Replace("\"", ""))
                    {
                        f.Delete();
                    }
                }
            }
        }

        #endregion
    }
View Code
soscw.com,搜素材soscw.com,搜素材
unity.RegisterType();
unity.RegisterType(new    HierarchicalLifetimeManager());
//......
public class UsersController : ApiController
{
    readonly IUsersRepository repository;

    public UsersController(IUsersRepository repository)
    {
        this.repository = repository;
    }
     //..........
}
View Code

最后的结构图如下: 

soscw.com,搜素材

 

二、RoleProvider 角色管理

功能:提供用户角色的管理、验证相关方法。

   同上,BlogEngine提供了DbRoleProvider和XmlRoleProvider。而且通过配置文件加入了系统角色。在BlogConfig.cs文件中可以看到,他提供了三个系统角色,管理员,匿名用户和编辑。

soscw.com,搜素材soscw.com,搜素材
#region AdministratorRole

        private static string _administrativeRole;

        /// 
        ///     The role that has administrator persmissions
        /// 
        public static string AdministratorRole
        {
            get
            {
                return _administrativeRole ?? (_administrativeRole = WebConfigurationManager.AppSettings["BlogEngine.AdminRole"] ?? "administrators");
            }
        }
        #endregion

        #region AnonymousRole

        private static string _anonymousRole;

        /// 
        /// The role that represents all non-authenticated users.
        /// 
        public static string AnonymousRole
        {
            get
            {
                return _anonymousRole ?? (_anonymousRole = WebConfigurationManager.AppSettings["BlogEngine.AnonymousRole"] ?? "Anonymous");
            }
        }

        #endregion

        #region EditorsRole

        private static string _editorsRole;

        /// 
        /// The role that represents all non-authenticated users.
        /// 
        public static string EditorsRole
        {
            get
            {
                return _editorsRole ?? (_editorsRole = WebConfigurationManager.AppSettings["BlogEngine.EditorsRole"] ?? "Editors");
            }
        }

        #endregion
View Code

在Web.config的AppSettings的节点可以看到,且这样可以比较方便的修改默认名称。

 add key="BlogEngine.AdminRole" value="Administrators" />
    
    add key="BlogEngine.AnonymousRole" value="Anonymous" />
    
    add key="BlogEngine.EditorsRole" value="Editors" />

 1.以XmlRoleProvider为例。(先不必纠结代码中Blog.CurrentInstance.Id)

soscw.com,搜素材soscw.com,搜素材
 public class XmlRoleProvider : RoleProvider
    {
//...............
 public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            ReadRoleDataStore();

            var currentRoles = new Liststring>(this.GetAllRoles());
            if (usernames.Length != 0 && roleNames.Length != 0)
            {
                foreach (var rolename in roleNames.Where(rolename => !currentRoles.Contains(rolename) && !rolename.Equals(BlogConfig.AnonymousRole, StringComparison.OrdinalIgnoreCase)))
                {
                    this.roles[Blog.CurrentInstance.Id].Add(new Role(rolename, new Liststring>(usernames)));
                }

                foreach (var role in this.roles[Blog.CurrentInstance.Id])
                {
                    var role1 = role;
                    foreach (var s in from name in roleNames
                                      where role1.Name.Equals(name, StringComparison.OrdinalIgnoreCase)
                                      from s in usernames
                                      where !role1.Users.Contains(s)
                                      select s)
                    {
                        role.Users.Add(s);
                    }
                }
            }

            this.Save();
        }

        /// 
        /// Adds a new role to the data source for the configured applicationName.
        /// 
        /// 
        /// The name of the role to create.
        /// 
        public override void CreateRole(string roleName)
        {
            ReadRoleDataStore();

            // This needs to be fixed. This will always return false.
            if (this.roles[Blog.CurrentInstance.Id].Contains(new Role(roleName)))
            {
                return;
            }

            this.roles[Blog.CurrentInstance.Id].Add(new Role(roleName));
            this.Save();
        }
}
View Code

 一个角色可以包含多个用户。Role对象如下,便于存储。

soscw.com,搜素材soscw.com,搜素材
public class Role
    {
        #region Constructors and Destructors

        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// 
        /// A name of the role.
        /// 
        public Role(string name) : this(name, new Liststring>())
        {
        }

        /// 
        ///     Initializes a new instance of the  class.
        /// 
        public Role() : this(null, new Liststring>())
        {
        }

        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// 
        /// A name of the role.
        /// 
        /// 
        /// A list of users in role.
        /// 
        public Ro


评论


亲,登录后才可以留言!