摘要:asp.net 2.0的membership組件提供了一組非常簡單易用的接口供開發(fā)者進(jìn)行用戶管理,用戶驗(yàn)證。本文將它對它的實(shí)現(xiàn)原理進(jìn)行簡單的分析,介紹如何正確的使用,以及如何對它進(jìn)擴(kuò)展。
一、membershipprovider抽象類
在很多情況下,在使用membership的時(shí)候我們并不會(huì)直接使用到這個(gè)類。在membershipprovider類定義的都是一些抽象方法和抽象屬性,就是這些方法和屬性構(gòu)成了membership接口的基本規(guī)范,而且在.net 框架內(nèi)部使用membership的功能都是通過這個(gè)類型調(diào)用的。繼承類通過實(shí)現(xiàn)這些接口來提供不用環(huán)境下的用戶管理功能,并且對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是幾個(gè)方法是密碼的輔助方法,用于加密,解密和驗(yàn)證密碼。但這邊的設(shè)計(jì)似乎有一些問題,將這些方法定義為internal范圍好像有點(diǎn)不妥,將這些方法定義在基類中,就是為了能夠被復(fù)用,但是從效果上來看,不是這樣的,因?yàn)閕nternal的成員只允許在本程序集內(nèi)被使用(正常情況下,不包括反射等其它方法),這就是說我們自己擴(kuò)展的membershipprovider是無法使用到這些方法的。而且從目前應(yīng)用范圍來看,目前這些方法也只有在sqlmembershipprovider中被使用到,所以我認(rèn)為應(yīng)該將這些方法修飾符修改為protected。 二、membership 靜態(tài)類
上面提到過,一般情況下我們都不會(huì)直接去使用到membershipprovider抽象,因?yàn)檫@涉及到如何去實(shí)例化真正的membership服務(wù)類的問題,涉及到配置和實(shí)例化對象的問題一般都是比較棘手的問題,對初學(xué)者來說,想要掌握也不是那么容易。那在.net框架中就是通過membership(static class)這個(gè)靜態(tài)類來屏蔽掉這一層的復(fù)雜關(guān)系。membership(static class)除了使用者屏蔽讀配置文件,初始對象等一些基本工作外,還有一個(gè)重要的作用就是重載所有的membershipprovider所以有api,甚至為了讓用戶更加方便的使用,將這些方法重載為靜態(tài)方法,并且提供了membershipprovider基本api基礎(chǔ)上更加豐富的重載實(shí)現(xiàn)供使用者調(diào)用。這就直接支持了不管是在ui層,還是其它的各個(gè)工程,只需要引用system.web.security命名空間,就可以不用關(guān)心任何細(xì)節(jié)的享受到membership給我們提供的各種便利。下面來看看membership(static class)的原型定義:(利用lutz roder’s .net reflector可以查看它的所有實(shí)現(xiàn)。)
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)實(shí)現(xiàn)代碼的過程中,可以發(fā)現(xiàn),每一個(gè)membersip api重載都最后都是調(diào)用屬性provider的方法,這個(gè)屬性的類型就是membershipprovider類型,只有看到這里,你也許才會(huì)理解membershipprovider的重要作用了吧。還有一個(gè)providers屬性,這個(gè)屬性就是獲得web.config中配置的所有的membership提供服務(wù)類。它們都是靜態(tài)屬性,但是它們怎么去實(shí)例化的呢?就是通過調(diào)用membership. initialize()這個(gè)方法,在每次調(diào)用這兩個(gè)屬性的時(shí)候,都會(huì)調(diào)用這個(gè)方法去判斷是否已初始化了membership提供服務(wù)類了,如果沒有則去調(diào)用配置服務(wù)類,讀取配置內(nèi)容,從而進(jìn)行初始化。到此你可能也就不難理解了,為什么我們使用那么簡單了!
三、sqlmembershipprovider介紹和使用配置
ok,通過上面的介紹應(yīng)該基本可以了解membership的整體結(jié)構(gòu)了吧?(如何還沒有,可能是你沒有打開lutz roder’s .net reflector去分析它的實(shí)現(xiàn)代碼,或者是對抽象類的作用還沒弄明白)。不管怎么樣,我們最終的目的就是要學(xué)會(huì)如何去使用。
在這之前,我先要介紹一下,在.net 框架中提供的兩個(gè)membershipprovider實(shí)現(xiàn)類:activedirectorymembershipprovider和sqlmembershipprovider(如何知道這兩個(gè)類的?在membershipprovider的derived types就可以看到所有的繼承類了。)前者是提供基本活動(dòng)目錄下的用戶管理(我也沒有實(shí)踐過),后者就是我們最經(jīng)常使用到的基于sqlserver的用戶管理實(shí)現(xiàn)。
到了介紹如何使用了,其實(shí)園子里已經(jīng)有了這方面的文章((翻譯)怎么在asp.net 2.0中使用membership),我也不多費(fèi)口舌了。但這邊要告訴大家一個(gè)最直接的學(xué)習(xí)和參考使用的辦法。在系統(tǒng)盤找到并打開machine.config,找到aspnetsqlmembershipprovider節(jié)點(diǎn):
<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>
看到?jīng)]有,其實(shí)這個(gè)就是一個(gè)最基本的membership配置了,只不過是還少了一個(gè)defaultprovider屬性的指定,指定了這個(gè)屬性后,你再使用login控件,進(jìn)行用戶登錄驗(yàn)證就無需使用任何代碼了。不信你可以試試。(關(guān)于forms驗(yàn)證,就不在這里多做介紹,可以參考相關(guān)資料。關(guān)于sqlmembershipprovider的更多屬性的介紹可以參看msdn)。
四、如何自定義membershipprovider,現(xiàn)有其它的membershipprovider資源
那么,我們?nèi)绾稳プ远x一個(gè)membershipprovider呢?其實(shí)如果你已經(jīng)了解了membership的結(jié)構(gòu)后我相信對你來說已經(jīng)不是一件很難的事了,但是考慮到要完整的寫一個(gè)membershipprovider還是有一定的工作量和難度的。對于我們來說,更多的地方可能是對現(xiàn)有的provider進(jìn)行擴(kuò)展,如sqlmembershipprovider。那其實(shí)這是非常簡單的,我們只需要繼承自sqlmembershipprovider,(悄悄告訴你,在initialize方法config參數(shù)中保存的就是provider對應(yīng)配置節(jié)的屬性名和值)然后擴(kuò)展和重寫所需的方法就可以了。使用的時(shí)候,在provider配置節(jié)中,將type的值改為你的類名就ok了。