标签: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
等。
(完整的源码可以去官网下载,这里不列出了)
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来指定。
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>
这里看到的SqlMembershipProvider是在.net2.0中就自带的一个Provider。
3.那这样就可以在我们的AccountController中调用了。
[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);
}
另外还封装了一个UsersRepository,并通过API的方式供外部使用。
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
unity.RegisterType();
unity.RegisterType(new HierarchicalLifetimeManager());
//......
public class UsersController : ApiController
{
readonly IUsersRepository repository;
public UsersController(IUsersRepository repository)
{
this.repository = repository;
}
//..........
}
View
Code
最后的结构图如下:
二、RoleProvider 角色管理
功能:提供用户角色的管理、验证相关方法。
同上,BlogEngine提供了DbRoleProvider和XmlRoleProvider。而且通过配置文件加入了系统角色。在BlogConfig.cs文件中可以看到,他提供了三个系统角色,管理员,匿名用户和编辑。
#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)
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对象如下,便于存储。
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