最近因?yàn)轫?xiàng)目代碼重構(gòu)需要重新整理用戶登錄和權(quán)限控制的部分,現(xiàn)有的代碼大體是參照了.NET的FORM認(rèn)證,并結(jié)合了PORTAL KITS的登錄控制,代碼比較啰嗦,可維護(hù)性比較差。于是有了以下的幾個(gè)需求(大多數(shù)系統(tǒng)應(yīng)該都會(huì)碰到):
1.用.NET自帶的FORM認(rèn)證來實(shí)現(xiàn)安全登錄
2.登錄后需要記錄登錄用戶的基本信息,方便所有頁面調(diào)用
3.記錄本機(jī)登錄狀態(tài),短時(shí)間關(guān)閉窗口后不用重新登錄
4.權(quán)限控制和代碼的文件夾結(jié)構(gòu)相呼應(yīng),即按角色允許訪問不同的目錄
5.權(quán)限控制有可能需要細(xì)化到每一個(gè)頁面,即按角色允許訪問不同的頁面
6.以上的部分盡量自己少寫代碼,用自帶的類庫和機(jī)制實(shí)現(xiàn)
第一步:準(zhǔn)備工作
先準(zhǔn)備一個(gè)名為Test的WEB項(xiàng)目,包含:
Default.aspx,默認(rèn)頁,隨便顯示一些信息,
Login.aspx,登錄頁,上面放兩個(gè)文本框,用來輸入用戶名和密碼,一個(gè)登錄按鈕,一個(gè)指向Register.aspx的超鏈,
Register.aspx,用戶注冊(cè)頁,注冊(cè)用戶信息,隨便放一點(diǎn)文本框,主要是模擬一下注冊(cè),不用真正實(shí)現(xiàn),
Web.config,配置頁面。
注冊(cè)頁與登錄頁在同一目錄的機(jī)妙后面會(huì)說。
第二步:Web.config文件的修改
1、打開Web.config文件,找到authentication節(jié),將其改為如下:
authentication mode="Forms">
forms name=".ASPXAUTH" loginUrl="Login.aspx" protection="All" path="/" timeout="20"/>
/authentication>
authorization>
deny users="?">/deny>
/authorization>
配置節(jié)屬性的具體意義和其他沒有加入的屬性網(wǎng)上到處都有查。這里注意一下的是authentication節(jié)和authorization節(jié),兩個(gè)單詞很相似,但卻不是同一個(gè)單詞,每個(gè)節(jié)下面的內(nèi)容也不能寫到一起。
其中,authorization節(jié)中的“allow”表示允許的意思,“*”表示所有用戶;而“deny”表示拒絕的意思;“?”表示匿名用戶;此處加入后,則代表根目錄下的所有文件和所有的子目錄都不能匿名訪問,Login.aspx 頁面除外。
2、Web.config中,Location節(jié)的應(yīng)用
做了上面的配置之后,我們會(huì)發(fā)現(xiàn),在沒登錄的情況下,用瀏覽器打開Default.aspx會(huì)自動(dòng)轉(zhuǎn)到Login.aspx,同理Register.aspx頁面也會(huì)如此。問題:注冊(cè)用戶怎么可能在登錄后才能訪問呢?
那么我們就得說了,當(dāng)注冊(cè)頁與登錄頁在同一目錄,為了達(dá)到不用登錄就能訪問注冊(cè)頁的目的,我們就得對(duì)訪問限制的Web.config配置處理一下。
方法一 :注冊(cè)頁與登錄頁放在不的目錄內(nèi)
我們?cè)诟夸浱砑右粋€(gè)文件夾Pub,將Register.aspx移動(dòng)到此文件夾里,此時(shí)仍不能訪問,需要在文件夾內(nèi)添加一個(gè)Web.config文件,加入:
configuration>
system.web>
authorization>
allow users="*"/>
/authorization>
/system.web>
/configuration>
此處,即說明此目錄下的所有文件,允許所有人訪問。
關(guān)于 Web.config 作用范圍的說明:
• Web.config 的設(shè)置將作用于所在目錄的所有文件及其子目錄下的所有東東(繼承:子隨父姓)
• 子目錄下的 Web.config 設(shè)置將覆蓋由父目錄繼承下來的設(shè)置(覆蓋:縣官不如現(xiàn)管)
• 也就是,屬性設(shè)置由最深一層的目錄里的Web.config決定;如果子目錄里沒有Web.config文件,則由離它最近的父目錄里的Web.config決定
方法二:仍然保持注冊(cè)頁和登錄頁在同一目錄下
只需要在根目錄下的Web.config 中加入以下一段:
location path="Register.aspx">
system.web>
authorization>
allow users="*"/>
/authorization>
/system.web>
/location>
通過location節(jié)的path屬性的值指定Register.aspx頁面,以及下面authorization節(jié)的設(shè)置,說明了Register.aspx頁面是允許被所有人訪問。
注意:
location節(jié)應(yīng)加在原有的system.web節(jié)的外面,包含在configuration節(jié)內(nèi),和system.web節(jié)是同級(jí)的?! ?/span>
當(dāng)根目錄下,有多個(gè)頁面不需要登錄就可以訪問時(shí),可以設(shè)置多個(gè)location節(jié),修改對(duì)應(yīng)path屬性值指向的頁面就可以了。
另外,path屬性的值也可以指定目錄,用來指定該目錄的訪問限制。通過修改authorization節(jié)的內(nèi)容來限定訪問權(quán)限。詳細(xì)的設(shè)置,后面會(huì)提到。
第三步:實(shí)現(xiàn)登錄的代碼
1、普通的代碼實(shí)現(xiàn)
方法一:
如果forms節(jié)中設(shè)置了“defaultUrl”的屬性,也就是登錄后默認(rèn)轉(zhuǎn)向的頁面,則可以用如下的方法:
private void Btn_Login_Click(object sender, System.EventArgs e)
{
if(this.Txt_UserName.Text=="Admin" this.Txt_Password.Text=="123456")
{
FormsAuthentication.RedirectFromLoginPage(this.Txt_UserName.Text,false);
}
}
此處只是簡(jiǎn)單模擬了一下登錄的驗(yàn)證過程,RedirectFromLoginPage方法能發(fā)送驗(yàn)證票據(jù)驗(yàn)證Cookie(如何進(jìn)行可以用Reflector去查看源代碼),返回請(qǐng)求頁面,即“從哪來就打哪去”。比如:用戶沒登錄前直接在 IE 地址欄輸入 http://localhost/Test/Default.aspx ,那么該用戶將看到的是 Login.aspx?ReturnUrl=Default.aspx ,輸入用戶名與密碼登錄成功后,系統(tǒng)將根據(jù)“ReturnUrl”的值,返回相應(yīng)的頁面;如果沒有“ReturnUrl”,則按照“defaultUrl”的屬性自動(dòng)轉(zhuǎn)向。
方法二:
private void Btn_Login_Click(object sender, System.EventArgs e)
{
if(this.Txt_UserName.Text=="Admin" this.Txt_Password.Text=="123456")
{
FormsAuthentication.SetAuthCookie(this.Txt_UserName.Text,false);
Response.Redirect("Default.aspx");
}
}
此處是分兩步走:通過驗(yàn)證后就直接發(fā)放 Cookie ,跳轉(zhuǎn)頁面將由程序員自行指定,無需“defaultUrl”設(shè)置。此方法對(duì)于程序員來說,更靈活?! ?/p>
2、手工實(shí)現(xiàn)需要記錄用戶登錄信息的情況
當(dāng)我們需要記錄用戶登錄的信息,不單單只是一個(gè)ID還需要更多屬性的時(shí)候,一般都用一個(gè)類存儲(chǔ)到Session或Cookie實(shí)現(xiàn),然后做一個(gè)基類頁,在基類頁中設(shè)置屬性來讀取Session或Cookie。
Session實(shí)際也和支不支持Cookie有關(guān),且存在服務(wù)器,多少會(huì)占用服務(wù)器端資源。因此這里還是考慮用Cookie實(shí)現(xiàn)。那么在RedirectFromLoginPage方法或SetAuthCookie方法已經(jīng)設(shè)置了驗(yàn)證票據(jù)并設(shè)置了Cookie,我們能不能把用戶登錄信息也存儲(chǔ)到這個(gè)默認(rèn)的Cookie里呢?答案是能。
首先,我們?cè)陧?xiàng)目里添加AppCode目錄,增加一個(gè)UserInfo的類,用以簡(jiǎn)單模擬用戶登錄信息。代碼如下:
[Serializable]
public class UserInfo
{
//用戶登錄信息
private int _nId;
private string _sRealName;
private string _sName;
private string _sPassword;
private string _sRoles;
public int Id
{
get { return this._nId; }
set { this._nId = value; }
}
public string RealName
{
get { return this._sRealName; }
set { this._sRealName = value; }
}
public string Name
{
get { return this._sName; }
set { this._sName = value; }
}
public string Password
{
get { return this._sPassword; }
set { this._sPassword = value; }
}
public string Roles
{
get { return this._sRoles; }
set { this._sRoles = value; }
}
public UserInfo()
{
}
}
需要注意, 類的屬性中一定要加[Serializable],表示類可以序列化。
Forms驗(yàn)證在內(nèi)部的機(jī)制是,把用戶數(shù)據(jù)加密后保存在一個(gè)基于cookie的票據(jù)FormsAuthenticationTicket中,通過RedirectFromLoginPage方法或SetAuthCookie方法就已經(jīng)實(shí)現(xiàn)了Ticket和Cookie的設(shè)置,也就是設(shè)置了Context.User的值,Context.User在取值和判斷是否經(jīng)過驗(yàn)證的時(shí)候很有用處。Cookie的屬性是在Web.config的forms name=".ASPXAUTH" loginUrl="Login.aspx" protection="All" path="/" timeout="20"/>中設(shè)置的。因?yàn)槭墙?jīng)過特殊加密的,所以應(yīng)該來說是比較安全的。
而.net除了用這個(gè)票據(jù)存放自己的信息外,還留了一個(gè)地給用戶自由支配,這就是現(xiàn)在要說的Ticket的UserData。 UserData用來存儲(chǔ)string類型的信息,并且也享受Forms驗(yàn)證提供的加密保護(hù),當(dāng)我們需要這些信息時(shí),也可以通過簡(jiǎn)單的Ticket的 UserData屬性得到,兼顧了安全性和易用性,用來保存一些必須的敏感信息還是很有用的。我們就準(zhǔn)備將用戶的登錄信息記錄在UserData中,代碼如下:
protected void Button1_Click(object sender, EventArgs e)
{
if (this.TextBox1.Text == "Admin" this.TextBox2.Text == "123456")
{
// 加密UserInfo
UserInfo user = new UserInfo();
user.Id = 1;
user.Name = this.TextBox1.Text;
user.Password = this.TextBox2.Text;
user.RealName = "系統(tǒng)管理員";
user.Roles = "Administrators,Users";
string strUser = Serialize.EncryptUserInfo>(user);
// 設(shè)置Ticket信息
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, user.Name, DateTime.Now, DateTime.Now.AddMinutes(20), false, strUser);
// 加密驗(yàn)證票據(jù)
string strTicket = FormsAuthentication.Encrypt(ticket);
// 使用新userdata保存cookie
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strTicket);
cookie.Expires = ticket.Expiration;
this.Response.Cookies.Add(cookie);
this.Response.Redirect("Default.aspx");
}
}
上面的代碼,實(shí)際上類似于手工實(shí)現(xiàn)了SetAuthCookie方法的過程。
首先,模擬實(shí)現(xiàn)登錄,我們手動(dòng)設(shè)置了一個(gè)UserInfo的對(duì)象,string strUser = Serialize.EncryptUserInfo>(user) 是將對(duì)象序列化成字符串的一個(gè)方法。
然后,生成一個(gè)FormsAuthenticationTicket票據(jù)。此處用到的FormsAuthenticationTicket構(gòu)造函數(shù)的重載方法的簽名解釋
public FormsAuthenticationTicket(
int version, //版本號(hào)
string name, //與身份驗(yàn)證票關(guān)聯(lián)的用戶名
DateTime issueDate, //票據(jù)的發(fā)出時(shí)間
DateTime expiration,//票據(jù)的到期日期
bool isPersistent, //票據(jù)是否存儲(chǔ)在持久的 Cookie 中,是為 true;否則為 false
string userData //票據(jù)中存儲(chǔ)的用戶定義數(shù)據(jù)
);
其中,name的設(shè)置與Context.User.Identity.Name對(duì)應(yīng),且大小寫敏感,也與將來的權(quán)限控制相關(guān),賦值的時(shí)候需要特別注意。另外,票據(jù)的到期日期和Web.config中設(shè)置的Cookie的到期日期不是同一個(gè)概念,如果分不清,請(qǐng)到網(wǎng)上去搜索,如果實(shí)在不想在這上下功夫,后面會(huì)有處理的方法。
再后,string strTicket = FormsAuthentication.Encrypt(ticket) 將票據(jù)加密成字符創(chuàng)
最后,HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strTicket) 生成Cookie。
FormsAuthentication.FormsCookieName獲取的就是Web.config中配置的Cookie名稱,也就是默認(rèn)驗(yàn)證時(shí)產(chǎn)生的Cookie。cookie.Expires = ticket.Expiration 將票據(jù)的過期時(shí)間和Cookie的過期時(shí)間做了同步,也就避免了兩者不同所產(chǎn)生的矛盾。這樣,驗(yàn)證票據(jù)生成了,存儲(chǔ)到默認(rèn)配置的Cookie中,也就是類似實(shí)現(xiàn)了一個(gè)SetAuthCookie方法的過程。通過Context.User就能獲取票據(jù)的相關(guān)信息了。
3、獲取信息
為了在其他登錄后的頁面比較簡(jiǎn)單的獲取登錄用戶信息,我們先生成一個(gè)基類頁面。在AppCode中新增LoginBasePage類,代碼如下:
public class LoginBasePage : Page
{
protected UserInfo LoginUser
{
get
{
string strUser = ((FormsIdentity)this.Context.User.Identity).Ticket.UserData;
return Serialize.DecryptUserInfo>(strUser);
}
}
public LoginBasePage()
{
//
// TODO: 在此處添加構(gòu)造函數(shù)邏輯
//
}
}
LoginBasePage : Page,基類頁要繼承Page,成為所有登錄以后的頁面的基類。
屬性protected UserInfo LoginUser{ get;}用來訪問登錄信息。將Context.User.Identity強(qiáng)制轉(zhuǎn)換為FormsIdentity類的對(duì)象,通過訪問Ticket屬性的UserData屬性,獲得被序列化后的對(duì)象的字符串,最后用方法Serialize.DecryptUserInfo>(strUser)將字符串反序列化成對(duì)象后再返回UserInfo類型的對(duì)象。
我們只需要將Default頁面的后臺(tái)代碼改為public partial class _Default : LoginBasePage,就可以通過this.LoginUser來訪問用戶登錄信息了。
第四步:實(shí)現(xiàn)不同目錄的權(quán)限控制
上面,實(shí)現(xiàn)了記錄用戶登錄信息的模擬登錄過程,以及根目錄下文件的訪問控制。但是系統(tǒng)一般都會(huì)有多個(gè)目錄,接下來就說說目錄的訪問控制。
其實(shí),上面多多少少已經(jīng)提到過了,通過在每個(gè)目錄下增加Web.config文件來進(jìn)行訪問限制。
首先,我們?cè)诟夸浽黾右粋€(gè)文件夾ManageAdmin,在此文件夾內(nèi)增加頁面UserInfo.aspx,頁面內(nèi)放幾個(gè)Label用來展現(xiàn)登錄用戶信息。
然后,再增加一個(gè)Web.config文件,配置內(nèi)容如下:
configuration>
appSettings/>
connectionStrings/>
system.web>
authorization>
allow users="Admin">/allow>
deny users="*">/deny>
/authorization>
/system.web>
/configuration>
配置中說明只允許“Admin”用戶訪問,禁止其他所有用戶訪問。
這里,特別要注意的是,F(xiàn)ormsAuthenticationTicket票據(jù)的name屬性的賦值,一定要和allow users="Admin">/allow>設(shè)置的用戶想對(duì)應(yīng),且大小寫敏感。如果要設(shè)置允許多個(gè)用戶訪問,則用“,”隔開,例如allow users="Admin,User1">/allow>。
不同的目錄,設(shè)置不同的允許訪問的用戶,就可以對(duì)所有目錄進(jìn)行訪問控制了。
第五步:實(shí)現(xiàn)不同目錄的按角色的權(quán)限控制
以上實(shí)現(xiàn)了對(duì)不同目錄按用戶的訪問限制。但是一般來說,一個(gè)網(wǎng)站系統(tǒng)的用戶會(huì)很多,如果一直使用精確到用戶的訪問控制,則會(huì)造成設(shè)置Web.config的工作量加大。
而一般,我們會(huì)將用戶分到不同的用戶組來進(jìn)行權(quán)限控制,因此,我們也可以配置Web.config實(shí)現(xiàn)按角色來控制不同的目錄的訪問權(quán)限。
首先,我們?cè)诟夸浵略僭黾右粋€(gè)目錄ManageUsers,在此文件夾內(nèi)也增加頁面UserInfo.aspx用來展現(xiàn)登錄用戶信息。此目錄將模擬控制Users組的用戶,文件夾ManageAdmin將模擬控制Administrators組的用戶。
然后,在目錄ManageUsers增加Web.config文件,配置內(nèi)容如下:
configuration>
appSettings/>
connectionStrings/>
system.web>
authorization>
allow roles="Users">/allow>
deny users="*">/deny>
/authorization>
/system.web>
/configuration>
再將文件夾ManageAdmin下的Web.config文件的allow users="Admin">/allow>改成allow roles="Administrators">/allow>。
最后,修改代碼。
1、注意,我們?cè)谀M用戶信息的時(shí)候,有這么一句,user.Roles = "Administrators,Users";也就是用戶Admin具備兩種角色
2、為模擬Users組的用戶登錄,我們?cè)偬砑尤缦麓a:
if (this.TextBox1.Text == "User1" this.TextBox2.Text == "111111")
{
// 加密UserInfo
UserInfo user = new UserInfo();
user.Id = 2;
user.Name = this.TextBox1.Text;
user.Password = this.TextBox2.Text;
user.RealName = "普通用戶1";
user.Roles = "Users";
string strUser = Serialize.EncryptUserInfo>(user);
// 設(shè)置Ticket信息
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, user.Name, DateTime.Now, DateTime.Now.AddMinutes(20), false, strUser);
// 加密驗(yàn)證票據(jù)
string strTicket = FormsAuthentication.Encrypt(ticket);
// 使用新userdata保存cookie
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, strTicket);
cookie.Expires = ticket.Expiration;
this.Response.Cookies.Add(cookie);
this.Response.Redirect("Default.aspx");
}
這樣,我們登錄時(shí),輸入“Admin”和“User1”的時(shí)候,就可以模擬不同角色的用戶登錄了。
3、Forms基于角色的驗(yàn)證的內(nèi)部機(jī)制是,將角色的屬性也設(shè)置到了Context.User中,這里也需要手工代碼處理一下。
首先,為了支持基于角色的驗(yàn)證,我們每進(jìn)入一個(gè)頁面都需要將角色信息設(shè)置到Context.User中,那么最好的辦法就是在Global.asax 文件中的Application_AuthenticateRequest方法中設(shè)置。
Application_AuthenticateRequest方法,是在每次驗(yàn)證請(qǐng)求時(shí)觸發(fā),它與另外一個(gè)方法Application_BeginRequest的區(qū)別就在于,Application_AuthenticateRequest方法內(nèi),能夠訪問Context.User.Identity,而Application_BeginRequest則無法訪問。
我們?cè)诟夸浱砑右粋€(gè)Global.asax 文件,增加如下代碼:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (this.Context.User != null)
{
if (this.Context.User.Identity.IsAuthenticated)
{
if (this.Context.User.Identity is FormsIdentity)
{
string strUser = ((FormsIdentity)this.Context.User.Identity).Ticket.UserData;
string[] roles = Serialize.DecryptUserInfo>(strUser).Roles.Split(',');
this.Context.User = new GenericPrincipal(this.Context.User.Identity, roles);
}
}
}
}
此處,主要代碼就是將Context.User.Identity強(qiáng)制轉(zhuǎn)換為FormsIdentity類的對(duì)象,通過訪問Ticket屬性的UserData屬性,獲得被序列化后的對(duì)象的字符串,最后用方法Serialize.DecryptUserInfo>(strUser)將字符串反序列化成對(duì)象,再將UserInfo對(duì)象的Roles屬性以“,”為分隔符分隔成角色數(shù)組,再用Context.User.Identity和角色數(shù)組生成一個(gè)新的GenericPrincipal對(duì)象,賦值給Context.User ,則Context.User 為已經(jīng)設(shè)置好角色的驗(yàn)證對(duì)象。
按照我們的設(shè)置,Admin用戶能訪問兩個(gè)目錄,而User1用戶,則只能訪問ManageUsers一個(gè)目錄。
第六步:集中管理Web.config文件
目錄的訪問權(quán)限控制,是按用戶還是按角色,一般由具體業(yè)務(wù)決定。
但是,隨著目錄的增多,每個(gè)目錄下都存在一個(gè)Web.config文件,管理起來特別不方便。
通過上面提到過的location節(jié)的path屬性,我們可以實(shí)現(xiàn)Web.config配置的統(tǒng)一管理。我們可以將各個(gè)文件或目錄的配置都放置在根目錄的Web.config文件內(nèi),代碼如下:
configuration>
appSettings/>
connectionStrings/>
location path ="Register.aspx">
system.web>
authorization>
allow users="*"/>
/authorization>
/system.web>
/location>
location path ="ManageAdmin">
system.web>
authorization>
allow roles="Administrators">/allow>
deny users="*">/deny>
/authorization>
/system.web>
/location>
location path ="ManageUsers">
system.web>
authorization>
allow roles="Users">/allow>
deny users="*">/deny>
/authorization>
/system.web>
/location>
system.web>
!-- 這里放置原來根目錄 Web.config 的內(nèi)容,就不列出來了 -->
/system.web>
/configuration>
結(jié)尾:
這次徹底理順FORM驗(yàn)證的過程,發(fā)現(xiàn)了不少實(shí)用性很強(qiáng)的技巧,中間參考了很多網(wǎng)友的文章,也通過Reflector看了一下具體實(shí)現(xiàn)的源代碼。感覺收獲不少,最大的收獲就是對(duì)于問題不但要知其然更要知其所以然,要有一種打破沙鍋問到底的凈勝。
大家如果有什么問題有什么疑問,不但要找到解決的辦法,有時(shí)間的話最好從理論到底層代碼都好好過一過,對(duì)自己的水平長(zhǎng)進(jìn)有很大的幫助。
以上這篇Asp.Net實(shí)現(xiàn)FORM認(rèn)證的一些使用技巧(必看篇)就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
您可能感興趣的文章:- asp.net 特定目錄form驗(yàn)證
- ASP.NET的實(shí)用技巧詳細(xì)介紹
- .net實(shí)現(xiàn)網(wǎng)站用戶登錄認(rèn)證