ASP.NET中默认的MembershipProvider和RoleProvider是Sql Server的,要使用需要先在数据库aspnet_regsql注册一个对应的数据库
WebForm中提供的登录控件的验证是使用默认Membership实现的,可以完全不写后台代码拉几个控件就完成登录,注册,密码修改等功能
但是默认的提供类有时候不能满足要求,如数据库不是Sql Server,或者想使用自己的数据库表结构等原因不想使用自带的提供类,可以自定义提供类
主要就是实现2个抽象基类RoleProvider和MembershipProvider
网站结构:
在web.config中定义forms验证的路径和自定义提供类的名称
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0"/> <authentication mode="Forms"> <forms loginUrl="~/Login.aspx" timeout="60" > </forms> </authentication> <membership defaultProvider="AccessMembershipProvider"> <providers> <clear /> <add name="AccessMembershipProvider" type="WebApplication1.CustomProvider.AccessMembershipProvider" applicationName="/" /> </providers> </membership> <roleManager enabled="true" defaultProvider="InProcRoleProvider" > <providers> <clear/> <add name="InProcRoleProvider" type="WebApplication1.CustomProvider.InProcRoleProvider" applicationName="/"/> </providers> </roleManager> <pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/> </system.web> <location path="member"> <system.web> <authorization> <allow roles="member,admin"/> <deny users="*"/> </authorization> </system.web> </location> <location path="admin"> <system.web> <authorization> <allow roles="admin"/> <deny users="*"/> </authorization> </system.web> </location> </configuration>
其中定义了admin目录只能由admin的role成员访问,member目录可以由admin和member成员访问
MembershipProvider:
实现了ValidateUser和几个必要的验证属性(MinRequiredPasswordLength等)就可以使登录控件正常使用,其他注册,密码修改等控件实现对应方法即可
这里我使用了Access作为数据源
先定义Access数据操作对象,连接串是固定的,实际情况应该是根据配置文件读取,传递进来
public class DaoAccess { private string conStr; public DaoAccess() { conStr = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|Database.mdb"; } private int ExecuteNonQuery(string query) { var con = new OleDbConnection(conStr); int rst = -1; OleDbCommand cmd = new OleDbCommand(query, con); try { con.Open(); rst = cmd.ExecuteNonQuery(); } finally { con.Close(); } return rst; } public bool CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer) { string query = string.Format("insert into UserInfo(Name,[Password],Email,PasswordQuestion,PasswordAnswer) values('{0}','{1}','{2}','{3}','{4}')", username, password, email, passwordAnswer, passwordQuestion); return ExecuteNonQuery(query)>0; } public bool ChangePassword(string username, string oldPassword, string newPassword) { string query = string.Format("update UserInfo set [Password]='{0}' where Name='{1}' and [Password]='{2}'", newPassword, username, oldPassword); return ExecuteNonQuery(query) > 0; } public UserInfo GetUserByName(string username) { string query = string.Format("select * from UserInfo where Name='{0}'", username); OleDbDataAdapter ada = new OleDbDataAdapter(query, new OleDbConnection(conStr)); DataTable dt = new DataTable(); ada.Fill(dt); var userlist= dt.ConvertToList<UserInfo>(); if (userlist.Count > 0) return userlist[0]; return null; }
然后自定义的AccessMembershipProvider中调用数据访问类来读取和写入access
public class AccessMembershipProvider:MembershipProvider { private string providerName ; private DaoAccess adapter = new DaoAccess(); public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config) { providerName = name; base.Initialize(name, config); } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override bool ChangePassword(string username, string oldPassword, string newPassword) { return adapter.ChangePassword(username, oldPassword, newPassword); } public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new NotImplementedException(); } public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { if (!adapter.CreateUser(username, password, email, passwordAnswer, passwordQuestion)) { status = MembershipCreateStatus.DuplicateUserName; } else { status = MembershipCreateStatus.Success; } var now=DateTime.Now; MembershipUser user = new MembershipUser(providerName, username, "", email, password, "" , true, true, now, now, now, now, now); return user; } public override bool DeleteUser(string username, bool deleteAllRelatedData) { throw new NotImplementedException(); } public override bool EnablePasswordReset { get { throw new NotImplementedException(); } } public override bool EnablePasswordRetrieval { get { throw new NotImplementedException(); } } public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); } public override int GetNumberOfUsersOnline() { throw new NotImplementedException(); } public override string GetPassword(string username, string answer) { throw new NotImplementedException(); } public override MembershipUser GetUser(string username, bool userIsOnline) { var user= adapter.GetUserByName(username); if (user == null) return null; var now=DateTime.Now; return new MembershipUser(providerName, user.Name, user.UserId, user.Email, user.PasswordQuestion, "", true, false, now, now, now, now, now); } public override MembershipUser GetUser(object providerUserKey, bool userIsOnline) { throw new NotImplementedException(); } public override string GetUserNameByEmail(string email) { throw new NotImplementedException(); } public override int MaxInvalidPasswordAttempts { get { return 20; } } public override int MinRequiredNonAlphanumericCharacters { get { return 0; } } public override int MinRequiredPasswordLength { get { return 6; } } //获取在锁定成员资格用户之前允许的最大无效密码或无效密码提示问题答案尝试次数的分钟数。 public override int PasswordAttemptWindow { get { return 20; } } public override MembershipPasswordFormat PasswordFormat { get { return MembershipPasswordFormat.Clear; } } public override string PasswordStrengthRegularExpression { get { throw new NotImplementedException(); } } public override bool RequiresQuestionAndAnswer { get { return true; } } public override bool RequiresUniqueEmail { get { return false; } } public override string ResetPassword(string username, string answer) { throw new NotImplementedException(); } public override bool UnlockUser(string userName) { throw new NotImplementedException(); } public override void UpdateUser(MembershipUser user) { throw new NotImplementedException(); } public override bool ValidateUser(string username, string password) { var user= adapter.GetUserByName(username); if (user == null) return false; return user.Password == password; } }
RoleProvider:
实现了IsUserInRole方法,就可以使role的验证功能正常,如果要其他增加删除等功能,实现对应方法即可
public class InProcRoleProvider : RoleProvider { //存放用户权限的字典 Dictionary<string, List<string>> _Users = new Dictionary<string, List<string>>(); //权限的列表 List<string> _Roles = new List<string>(); public override void AddUsersToRoles(string[] usernames, string[] roleNames) { CheckRoles(roleNames); foreach (var username in usernames) { foreach (var roleName in roleNames) AddUserToRole(username, roleName); } } private void CheckRoles(string[] roleNames) { foreach (var newRole in roleNames) { if (!_Roles.Any(item => item == newRole)) throw new Exception(newRole + " 不存在"); } } private void AddUserToRole(string username, string roleName) { if (_Users.ContainsKey(username)) { var roles = _Users[username]; if (roles.Any(name => name == roleName)) return; roles.Add(roleName); } else { _Users.Add(username, new List<string>() { roleName }); } } public override string ApplicationName { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public override void CreateRole(string roleName) { _Roles.Add(roleName); } public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) { _Roles.Remove(roleName); _Users= _Users.Where(item => { return item.Value.Any(v => v == roleName) == false; }) .ToDictionary(item => item.Key,item=>item.Value); return true; } public override string[] FindUsersInRole(string roleName, string usernameToMatch) { throw new NotImplementedException(); } public override string[] GetAllRoles() { throw new NotImplementedException(); } public override string[] GetRolesForUser(string username) { if (_Users.ContainsKey(username)) return _Users[username].ToArray(); return new string[0]; } public override string[] GetUsersInRole(string roleName) { throw new NotImplementedException(); } public override bool IsUserInRole(string username, string roleName) { if (_Users.ContainsKey(username)) { return _Users[username].Any(role=>role==roleName); } return false; } public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) { throw new NotImplementedException(); } public override bool RoleExists(string roleName) { return _Roles.Any(role => role == roleName); } }
为了简化代码,InProcRoleProvider用了内存作为存放用户的role数据
在Global.asax的Application_Start方法中初始化
void Application_Start(object sender, EventArgs e) { if(!Roles.RoleExists("admin")) Roles.CreateRole("admin"); if (!Roles.RoleExists("member")) Roles.CreateRole("member"); }
然后在页面部分,如果使用login的控件,默认是不会有role授权的,需要在onloggedin事件中将用户授权
页面部分:
<form id="form1" runat="server"> <div> 登录roles: <asp:DropDownList ID="DropDownList1" runat="server"> <asp:ListItem>member</asp:ListItem> <asp:ListItem Value="admin"></asp:ListItem> </asp:DropDownList> <br /> <asp:Login ID="Login1" runat="server" onloggedin="Login1_LoggedIn"> </asp:Login> <br /> <a href="Register.aspx">注册</a> </div> </form>
后台代码:
protected void Login1_LoggedIn(object sender, EventArgs e) { var user = Login1.UserName; string roleName = DropDownList1.SelectedValue; if (!Roles.IsUserInRole(user, roleName)) Roles.AddUserToRole(user, roleName); }
注册页可以直接使用
直接访问admin或者member目录都会跳转到登录页面
登陆成功并且相应的选择的role有权限访问对应目录的话,后登录控件会自动跳转到原请求页面
也可以不使用登陆控件,自己调用System.Web.Security中的Roles,Membership和FormsAuthentication静态类来操作登录授权和跳转等功能
Roles和Membership静态类调用的就是配置文件中设置的默认提供类,只是更为方便,内部会根据需要自动创建提供类的实例
示例代码: