ASP.NET2.0Membership原理及應用
摘要:asp.net 2.0的Membership組件提供了一組非常簡單易用的接口供開發者進行用戶管理,用戶驗證。本文將它對它的實現原理進行簡單的分析,介紹如何正確的使用,以及如何對它進擴展。
一、MembershipProvider抽象類
在很多情況下,在使用Membership的時候我們并不會直接使用到這個類。在MembershipProvider類定義的都是一些抽象方法和抽象屬性,就是這些方法和屬性構成了Membership接口的基本規范,而且在.NET 框架內部使用Membership的功能都是通過這個類型調用的。繼承類通過實現這些接口來提供不用環境下的用戶管理功能,并且對Membership框架本身并沒有影響,下面來看看MembershipProvider原形定義:
其中修飾符為internal是幾個方法是密碼的輔助方法,用于加密,解密和驗證密碼。但這邊的設計似乎有一些問題,將這些方法定義為internal范圍好像有點不妥,將這些方法定義在基類中,就是為了能夠被復用,但是從效果上來看,不是這樣的,因為internal的成員只允許在本程序集內被使用(正常情況下,不包括反射等其它方法),這就是說我們自己擴展的MembershipProvider是無法使用到這些方法的。而且從目前應用范圍來看,目前這些方法也只有在SqlMembershipProvider中被使用到,所以我認為應該將這些方法修飾符修改為protected。
二、Membership 靜態類
上面提到過,一般情況下我們都不會直接去使用到MembershipProvider抽象,因為這涉及到如何去實例化真正的Membership服務類的問題,涉及到配置和實例化對象的問題一般都是比較棘手的問題,對初學者來說,想要掌握也不是那么容易。那在.NET框架中就是通過Membership(Static Class)這個靜態類來屏蔽掉這一層的復雜關系。Membership(Static Class)除了使用者屏蔽讀配置文件,初始對象等一些基本工作外,還有一個重要的作用就是重載所有的MembershipProvider所以有API,甚至為了讓用戶更加方便的使用,將這些方法重載為靜態方法,并且提供了MembershipProvider基本API基礎上更加豐富的重載實現供使用者調用。這就直接支持了不管是在UI層,還是其它的各個工程,只需要引用System.Web.Security命名空間,就可以不用關心任何細節的享受到Membership給我們提供的各種便利。下面來看看Membership(Static Class)的原型定義:(利用Lutz Roder’s .NET Reflector可以查看它的所有實現。)
說到這里,就不得不多羅嗦兩句。在看Membership(Static Class)實現代碼的過程中,可以發現,每一個Membersip API重載都最后都是調用屬性Provider的方法,這個屬性的類型就是MembershipProvider類型,只有看到這里,你也許才會理解MembershipProvider的重要作用了吧。還有一個Providers屬性,這個屬性就是獲得web.config中配置的所有的Membership提供服務類。它們都是靜態屬性,但是它們怎么去實例化的呢?就是通過調用Membership. Initialize()這個方法,在每次調用這兩個屬性的時候,都會調用這個方法去判斷是否已初始化了Membership提供服務類了,如果沒有則去調用配置服務類,讀取配置內容,從而進行初始化。到此你可能也就不難理解了,為什么我們使用那么簡單了!
三、SqlMembershipProvider介紹和使用配置
OK,通過上面的介紹應該基本可以了解Membership的整體結構了吧?(如何還沒有,可能是你沒有打開Lutz Roder’s .NET Reflector去分析它的實現代碼,或者是對抽象類的作用還沒弄明白)。不管怎么樣,我們最終的目的就是要學會如何去使用。
在這之前,我先要介紹一下,在.NET 框架中提供的兩個MembershipProvider實現類:ActiveDirectoryMembershipProvider和SqlMembershipProvider(如何知道這兩個類的?在MembershipProvider的Derived Types就可以看到所有的繼承類了。)前者是提供基本活動目錄下的用戶管理(我也沒有實踐過),后者就是我們最經常使用到的基于SqlServer的用戶管理實現。
到了介紹如何使用了,其實園子里已經有了這方面的文章((翻譯)怎么在ASP.NET 2.0中使用Membership),我也不多費口舌了。但這邊要告訴大家一個最直接的學習和參考使用的辦法。在系統盤找到并打開machine.config,找到AspNetSqlMembershipProvider節點:
看到沒有,其實這個就是一個最基本的Membership配置了,只不過是還少了一個defaultProvider屬性的指定,指定了這個屬性后,你再使用Login控件,進行用戶登錄驗證就無需使用任何代碼了。不信你可以試試。(關于Forms驗證,就不在這里多做介紹,可以參考相關資料。關于SqlMembershipProvider的更多屬性的介紹可以參看MSDN)。
四、如何自定義MembershipProvider,現有其它的MembershipProvider資源
那么,我們如何去自定義一個MembershipProvider呢?其實如果你已經了解了Membership的結構后我相信對你來說已經不是一件很難的事了,但是考慮到要完整的寫一個MembershipProvider還是有一定的工作量和難度的。對于我們來說,更多的地方可能是對現有的Provider進行擴展,如SqlMembershipProvider。那其實這是非常簡單的,我們只需要繼承自SqlMembershipProvider,(悄悄告訴你,在Initialize方法config參數中保存的就是Provider對應配置節的屬性名和值)然后擴展和重寫所需的方法就可以了。使用的時候,在Provider配置節中,將type的值改為你的類名就OK了。
一、MembershipProvider抽象類
在很多情況下,在使用Membership的時候我們并不會直接使用到這個類。在MembershipProvider類定義的都是一些抽象方法和抽象屬性,就是這些方法和屬性構成了Membership接口的基本規范,而且在.NET 框架內部使用Membership的功能都是通過這個類型調用的。繼承類通過實現這些接口來提供不用環境下的用戶管理功能,并且對Membership框架本身并沒有影響,下面來看看MembershipProvider原形定義:
| public abstract class MembershipProvider : ProviderBase ...{ // Events public event MembershipValidatePasswordEventHandler ValidatingPassword; // Methods protected MembershipProvider(); public abstract bool ChangePassword(string username, string oldPassword, string newPassword); public abstract bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer); public abstract MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); protected virtual byte[] DecryptPassword(byte[] encodedPassword); public abstract bool DeleteUser(string username, bool deleteAllRelatedData); internal string EncodePassword(string pass, int passwordFormat, string salt); protected virtual byte[] EncryptPassword(byte[] password); public abstract MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords); public abstract MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords); internal string GenerateSalt(); public abstract MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords); public abstract int GetNumberOfUsersOnline(); public abstract string GetPassword(string username, string answer); public abstract MembershipUser GetUser(object providerUserKey, bool userIsOnline); public abstract MembershipUser GetUser(string username, bool userIsOnline); internal MembershipUser GetUser(string username, bool userIsOnline, bool throwOnError); public abstract string GetUserNameByEmail(string email); protected virtual void OnValidatingPassword(ValidatePasswordEventArgs e); public abstract string ResetPassword(string username, string answer); internal string UnEncodePassword(string pass, int passwordFormat); public abstract bool UnlockUser(string userName); public abstract void UpdateUser(MembershipUser user); public abstract bool ValidateUser(string username, string password); // Properties public abstract string ApplicationName ...{ get; set; } public abstract bool EnablePasswordReset ...{ get; } public abstract bool EnablePasswordRetrieval ...{ get; } public abstract int MaxInvalidPasswordAttempts ...{ get; } public abstract int MinRequiredNonAlphanumericCharacters ...{ get; } public abstract int MinRequiredPasswordLength ...{ get; } public abstract int PasswordAttemptWindow ...{ get; } public abstract MembershipPasswordFormat PasswordFormat ...{ get; } public abstract string PasswordStrengthRegularExpression ...{ get; } public abstract bool RequiresQuestionAndAnswer ...{ get; } public abstract bool RequiresUniqueEmail ...{ get; } // Fields private MembershipValidatePasswordEventHandler _EventHandler; private const int SALT_SIZE_IN_BYTES = 0x10; } |
其中修飾符為internal是幾個方法是密碼的輔助方法,用于加密,解密和驗證密碼。但這邊的設計似乎有一些問題,將這些方法定義為internal范圍好像有點不妥,將這些方法定義在基類中,就是為了能夠被復用,但是從效果上來看,不是這樣的,因為internal的成員只允許在本程序集內被使用(正常情況下,不包括反射等其它方法),這就是說我們自己擴展的MembershipProvider是無法使用到這些方法的。而且從目前應用范圍來看,目前這些方法也只有在SqlMembershipProvider中被使用到,所以我認為應該將這些方法修飾符修改為protected。
二、Membership 靜態類
上面提到過,一般情況下我們都不會直接去使用到MembershipProvider抽象,因為這涉及到如何去實例化真正的Membership服務類的問題,涉及到配置和實例化對象的問題一般都是比較棘手的問題,對初學者來說,想要掌握也不是那么容易。那在.NET框架中就是通過Membership(Static Class)這個靜態類來屏蔽掉這一層的復雜關系。Membership(Static Class)除了使用者屏蔽讀配置文件,初始對象等一些基本工作外,還有一個重要的作用就是重載所有的MembershipProvider所以有API,甚至為了讓用戶更加方便的使用,將這些方法重載為靜態方法,并且提供了MembershipProvider基本API基礎上更加豐富的重載實現供使用者調用。這就直接支持了不管是在UI層,還是其它的各個工程,只需要引用System.Web.Security命名空間,就可以不用關心任何細節的享受到Membership給我們提供的各種便利。下面來看看Membership(Static Class)的原型定義:(利用Lutz Roder’s .NET Reflector可以查看它的所有實現。)
| public static class Membership ...{ // Events public static event MembershipValidatePasswordEventHandler ValidatingPassword; // Methods static Membership(); public static MembershipUser CreateUser(string username, string password); public static MembershipUser CreateUser(string username, string password, string email); public static MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, out MembershipCreateStatus status); public static MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status); public static bool DeleteUser(string username); public static bool DeleteUser(string username, bool deleteAllRelatedData); public static MembershipUserCollection FindUsersByEmail(string emailToMatch); public static MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords); public static MembershipUserCollection FindUsersByName(string usernameToMatch); public static MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords); public static string GeneratePassword(int length, int numberOfNonAlphanumericCharacters); public static MembershipUserCollection GetAllUsers(); public static MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords); private static string GetCurrentUserName(); public static int GetNumberOfUsersOnline(); public static MembershipUser GetUser(); public static MembershipUser GetUser(bool userIsOnline); public static MembershipUser GetUser(object providerUserKey); public static MembershipUser GetUser(string username); public static MembershipUser GetUser(object providerUserKey, bool userIsOnline); public static MembershipUser GetUser(string username, bool userIsOnline); public static string GetUserNameByEmail(string emailToMatch); private static void Initialize(); public static void UpdateUser(MembershipUser user); public static bool ValidateUser(string username, string password); // Properties public static string ApplicationName ...{ get; set; } public static bool EnablePasswordReset ...{ get; } public static bool EnablePasswordRetrieval ...{ get; } public static string HashAlgorithmType ...{ get; } internal static bool IsHashAlgorithmFromMembershipConfig ...{ get; } public static int MaxInvalidPasswordAttempts ...{ get; } public static int MinRequiredNonAlphanumericCharacters ...{ get; } public static int MinRequiredPasswordLength ...{ get; } public static int PasswordAttemptWindow ...{ get; } public static string PasswordStrengthRegularExpression ...{ get; } public static MembershipProvider Provider ...{ get; } public static MembershipProviderCollection Providers ...{ get; } public static bool RequiresQuestionAndAnswer ...{ get; } public static int UserIsOnlineTimeWindow ...{ get; } // Fields private static char[] punctuations; private static bool s_HashAlgorithmFromConfig; private static string s_HashAlgorithmType; private static bool s_Initialized; private static Exception s_InitializeException; private static object s_lock; private static MembershipProvider s_Provider; private static MembershipProviderCollection s_Providers; private static int s_UserIsOnlineTimeWindow; } |
說到這里,就不得不多羅嗦兩句。在看Membership(Static Class)實現代碼的過程中,可以發現,每一個Membersip API重載都最后都是調用屬性Provider的方法,這個屬性的類型就是MembershipProvider類型,只有看到這里,你也許才會理解MembershipProvider的重要作用了吧。還有一個Providers屬性,這個屬性就是獲得web.config中配置的所有的Membership提供服務類。它們都是靜態屬性,但是它們怎么去實例化的呢?就是通過調用Membership. Initialize()這個方法,在每次調用這兩個屬性的時候,都會調用這個方法去判斷是否已初始化了Membership提供服務類了,如果沒有則去調用配置服務類,讀取配置內容,從而進行初始化。到此你可能也就不難理解了,為什么我們使用那么簡單了!
三、SqlMembershipProvider介紹和使用配置
OK,通過上面的介紹應該基本可以了解Membership的整體結構了吧?(如何還沒有,可能是你沒有打開Lutz Roder’s .NET Reflector去分析它的實現代碼,或者是對抽象類的作用還沒弄明白)。不管怎么樣,我們最終的目的就是要學會如何去使用。
在這之前,我先要介紹一下,在.NET 框架中提供的兩個MembershipProvider實現類:ActiveDirectoryMembershipProvider和SqlMembershipProvider(如何知道這兩個類的?在MembershipProvider的Derived Types就可以看到所有的繼承類了。)前者是提供基本活動目錄下的用戶管理(我也沒有實踐過),后者就是我們最經常使用到的基于SqlServer的用戶管理實現。
到了介紹如何使用了,其實園子里已經有了這方面的文章((翻譯)怎么在ASP.NET 2.0中使用Membership),我也不多費口舌了。但這邊要告訴大家一個最直接的學習和參考使用的辦法。在系統盤找到并打開machine.config,找到AspNetSqlMembershipProvider節點:
| <membership> <providers> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="LocalSqlServer" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" passwordAttemptWindow="10" passwordStrengthRegularExpression="" /> </providers> </membership> |
看到沒有,其實這個就是一個最基本的Membership配置了,只不過是還少了一個defaultProvider屬性的指定,指定了這個屬性后,你再使用Login控件,進行用戶登錄驗證就無需使用任何代碼了。不信你可以試試。(關于Forms驗證,就不在這里多做介紹,可以參考相關資料。關于SqlMembershipProvider的更多屬性的介紹可以參看MSDN)。
四、如何自定義MembershipProvider,現有其它的MembershipProvider資源
那么,我們如何去自定義一個MembershipProvider呢?其實如果你已經了解了Membership的結構后我相信對你來說已經不是一件很難的事了,但是考慮到要完整的寫一個MembershipProvider還是有一定的工作量和難度的。對于我們來說,更多的地方可能是對現有的Provider進行擴展,如SqlMembershipProvider。那其實這是非常簡單的,我們只需要繼承自SqlMembershipProvider,(悄悄告訴你,在Initialize方法config參數中保存的就是Provider對應配置節的屬性名和值)然后擴展和重寫所需的方法就可以了。使用的時候,在Provider配置節中,將type的值改為你的類名就OK了。