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靜態類調用的就是配置文件中設置的默認提供類,只是更為方便,內部會根據需要自動創建提供類的實例
示例代碼: