如何運用 Form 表單認證
ASP.NET 的安全認證,共有“Windows”“Form”“Passport”“None”四種驗證模式。“Windows”與“None”沒有起到保護的作用,不推薦使用;“Passport”我又沒用過,唉……是以我隻好講講“Form”認證了。我打算分三部分:
第一部分 —— 怎樣實作From 認證;
第二部分 —— Form 認證的實戰運用;
第三部分 —— 實作單點登入(Single Sign On)
第一部分 如何運用 Form 表單認證
一、 建立一個測試項目
為了更好說明,有必要建立一個測試項目(暫且為“FormTest”吧),包含三張頁面足矣(Default.aspx、Login.aspx、UserInfo.aspx)。啥?有人不會建立項目,不會新增頁面?你問我咋辦?我看這麼辦好了:拖出去,打回原藉,從幼稚園學起……
二、 修改 Web.config
1、 輕按兩下項目中的Web.config(不會的、找不到的打 PP)
2、 找到下列文字 <authentication mode="Windows" /> 把它改成:
<authentication mode="Forms">
<forms loginUrl="Login.aspx" name=".ASPXAUTH"></forms>
</authentication>
3、 找到<authorization> <allow users="*" /></authorization>換成
<authorization><deny users="?"></deny></authorization>
這裡沒什麼好說的,隻要拷貝過去就行。雖說如此,但還是有人會弄錯,如下:
<forms loginUrl="Login.aspx" name=".APSX"></forms>
<deny users="?"></deny>
若要問是誰把 <deny users="?"></deny> 放入 <authentication> 中的,我會很榮幸地告訴你,那是 N 年前的我:<authentication> 與 <authorization> 都是以 auth 字母開頭又都是以 ation 結尾,何其相似;英文單詞背不下來的我以為他們是一夥的……
三、 編寫 .cs 代碼——登入與退出
1、 登入代碼:
a、 書本上介紹的
private void Btn_Login_Click(object sender, System.EventArgs e)
{
if(this.Txt_UserName.Text=="Admin" && this.Txt_Password.Text=="123456")
{
System.Web.Security.FormsAuthentication.RedirectFromLoginPage(this.Txt_UserName.Text,false);
}
b、 偶找了 N 久才找到的
private void Btn_Login_Click(object sender, System.EventArgs e)
System.Web.Security.FormsAuthentication.SetAuthCookie(this.Txt_UserName.Text,false);
Response.Redirect("Default.aspx");
以上兩種都可發放驗證後的 Cookie ,即通過驗證,差別:
方法 a) 指驗證後傳回請求頁面,俗稱“從哪來就打哪去”。比如:使用者沒登入前直接在 IE 位址欄輸入 http://localhost/FormTest/UserInfo.aspx ,那麼該使用者将看到的是 Login.aspx?ReturnUrl=UserInfo.aspx ,輸入使用者名與密碼登入成功後,系統将根據“ReturnUrl”的值,傳回相應的頁面
方法 b) 則是分兩步走:通過驗證後就直接發放 Cookie ,跳轉頁面将由程式員自行指定,此方法多用于 Default.aspx 使用架構結構的系統。
2、 退出代碼:
private void Btn_LogOut_Click(object sender, System.EventArgs e)
{
System.Web.Security.FormsAuthentication.SignOut();
四、 如何判斷驗證與否及擷取驗證後的使用者資訊
有的時候,在同一張頁面需要判斷使用者是否已經登入,然後再呈現不同的布局。有人喜歡用 Session 來判斷,我不反對此類做法,在此我隻是想告訴大家還有一種方法,且看下面代碼:
if(User.Identity.IsAuthenticated)
//你已認證驗證,知道該怎麼做了吧?
User.Identity 還有兩個屬性AuthenticationType(驗證類型)與 Name(使用者名稱) ,大家要注意的是 Name 屬性,此處的User.Identity.Name将得到,驗證通過(RedirectFromLoginPage 或SetAuthCookie)時,我們帶入的第一個參數 this.Txt_UserName.Text 。這個參數很重要,關系到種種……種種的情況,何出此言,且聽下回分解……
靈活運用 Form 表單認證中的 deny 與 allow 及保護 .htm 等檔案
第二部分 Form 認證的實戰運用
Web.config 的作用範圍
建立項目時, VS.Net 會在項目根目錄建立一個内容固定的 Web.config。除了在項目根目錄,你還可以在任一目錄下建立 Web.config ,條件就是應用程式級别的節點隻能在根目錄的 Web.config 中出現。至于哪些是應用程式級别節點呢,這個問題嘛,其實我也不太清楚,呵呵。電腦不是我發明的,微軟不是我建立的,C# 更不是我說了算的,神仙也有不知道的,是以我不曉得是正常的。話雖如此,隻要它不報錯,那就是對的。
關于 Web.config 設定的作用範圍,記住以下兩點:
1、 Web.config 的設定将作用于所在目錄的所有檔案及其子目錄下的所有東東(繼承:子随父姓)
2、 子目錄下的 Web.config 設定将覆寫由父目錄繼承下來的設定(覆寫:縣官不如現管)
給大家提個問題:有沒有比根目錄Web.config 的作用範圍還大的配置檔案呢?看完第三部分便知分曉。
六、 學會拒絕與巧用允許
回到我們在第一回合建立的測試項目“FormTest” ,既然要進行驗證,按國際慣例,就得有使用者名與密碼。那,這些使用者是管理者自己在資料庫建好呢,還是使用者注冊、管理者稽核好呢。隻要不是一般的笨蛋,都知道選擇後者。你們還别說,我公司還真有個别項目是管理者連到資料庫去建帳号的,屬于比較特殊的笨蛋,咱們不學他也罷,還是老老實實添加兩個頁面吧——注冊頁面(Register.aspx)與稽核頁面(Auditing.aspx)。
問題終于就要浮出水面啦,當你做好 Register.aspx 時,想通路它的時候突然覺得不對勁,怎麼又回到了登入頁面?你仔細瞧瞧網址,是不是成了:Login.aspx?ReturnUrl=Register.aspx 。怎麼辦,使用者就是因為沒有帳号才去通路注冊頁面的呀?(這句純屬廢話,有帳号誰還跑去注冊。)我時常對我的同僚說:“辦法是人想出來滴!!”
1、 建立一個目錄 Public ,用于存放一些公用的檔案,如萬年曆、腳本呀……
2、 在“解決方案資料總管”中右擊點選目錄 Public ,新增一個 Web.config
3、 把上述 Web.config 的内容統統删除,僅留以下即可:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<authorization><allow users="*"/></authorization>
</system.web>
</configuration>
終于切入正題了,不容易呀。根據“覆寫”原則,我們知道上述 Web.config 将替代根目錄 Web.config 中的 <authorization> 節點設定,即:
<allow users="*"/> 替換 <deny users="?"></deny>
注解:“allow”允許的意思;“*”表示所有使用者;
“deny” 拒絕的意思;“?”表示匿名使用者;
是以,處于 Public 目錄下的檔案,允許所有人浏覽,包括未驗證的使用者。把 Register.aspx 拖進來吧,再也不會有人阻止你浏覽啦。
除了注冊頁面,我們還提到一個稽核頁面(Auditing.aspx),稽核權限一般都在管理者或主管手裡,并不想讓其他人浏覽此頁面(真理往往掌握在少數人的手裡,這也是沒法子的事),怎麼辦?“辦法是人想出來滴”呵呵……建立一個管理者的目錄 ManageSys ,在此目錄下再新增一個 Web.config。内容如下:
<authorization>
<allow users="Admin"/>
<deny users="*"/>
</authorization>
System.Web.Security.FormsAuthentication.SetAuthCookie(this.Txt_UserName.Text,false); //通過驗證,發放 Cookie
之前我曾強調,要注意,第一個參數很重要,重要到什麼程度?說到這,恐怕地球人都知道了——它就是allow與deny的依據。假如此處使用者填寫的是“Admin”即 this.Txt_UserName.Text = "Admin"; 那麼進入系統後,他就能通路 ManageSys 目錄下的網頁了,其它閑雜人等一律拒之門外。
以上 from
1: 在web.config中,加入form認證;
<authentication mode="Forms">
<forms name="auth" loginUrl="index.aspx" timeout="30"></forms>
<deny users="?" />
2: 如果有注冊頁面時還應該允許匿名使用者調用注冊頁面進行注冊;
以下代碼應該在<configuration><system.web>之間,而不應該包含到<system.web>..</system.web>之間;
----------------表示允許 匿名使用者對 userReg.aspx頁面進行通路.
<location path="userReg.aspx">
<system.web>
<authorization>
<allow users="?" />
</authorization>
</system.web>
</location>
3 在登入成功後要 建立身份驗證票, 表明已經通過認證的合法使用者;
if(登陸成功)
System.Web.Security.FormsAuthentication.SetAuthCookie(使用者名稱, false);
1.使用Forms驗證存儲使用者自定義資訊
Forms驗證在内部的機制為把使用者資料加密後儲存在一個基于cookie的票據FormsAuthenticationTicket中,因為是經過特殊加密的,是以應該來說是比較安全的。而.net除了用這個票據存放自己的資訊外,還留了一個地給使用者自由支配,這就是現在要說的UserData。
UserData可以用來存儲string類型的資訊,并且也享受Forms驗證提供的加密保護,當我們需要這些資訊時,也可以通過簡單的get方法得到,兼顧了安全性和易用性,用來儲存一些必須的敏感資訊還是很有用的。
下面來看怎麼使用UserData,然後會給出一個實際使用的例子。
//建立一個新的票據,将客戶ip記入ticket的userdata
FormsAuthenticationTicket ticket=new FormsAuthenticationTicket(
1,userName.Text,DateTime.Now,DateTime.Now.AddMinutes(30),
false,Request.UserHostAddress);
//将票據加密
string authTicket=FormsAuthentication.Encrypt(ticket);
//将加密後的票據儲存為cookie
HttpCookie coo=new HttpCookie(FormsAuthentication.FormsCookieName,authTicket);
//使用加入了userdata的新cookie
Response.Cookies.Add(coo);
下面是FormsAuthenticationTicket構造函數的重載之一的方法簽名
public FormsAuthenticationTicket(
int version,
string name,
DateTime issueDate,
DateTime expiration,
bool isPersistent,
string userData
);
參數
version
版本号。
name
與身份驗證票關聯的使用者名。
issueDate
Cookie 的發出時間。
expiration
Cookie 的到期日期。
isPersistent
如果 Cookie 是持久的,為 true;否則為 false。
userData
将存儲在 Cookie 中的使用者定義資料
使用userdata也很簡單,FormsIdentity的Ticket屬性就提供了對目前票據的通路,獲得票據後就可以用UserData屬性通路儲存的資訊,當然是經過解密的。
((System.Web.Security.FormsIdentity)this.Context.User.Identity).Ticket.UserData
下面是一個具體的應用。
由于Forms驗證是通過cookie來進行的,它需要傳遞一個票據來進行工作。雖然票據是加密的,裡面的内容不可見,但這并不能阻止别人用一個假冒的身份使用票據(就像我們可以拿别人的鑰匙去開别人的鎖),比較常見的就是不同ip的使用者在不安全通道截獲了這個票據,然後使用它進行一些安全範圍外的活動。
解決這個問題的辦法之一就是使用SSL來傳遞資訊。
但是如果不能使用SSL呢?我們可以判斷ip和票據是否比對,如果送出請求的ip是初次産生票據的ip,則沒有問題,否則就銷毀這個票據。
為此,我們需要在一開始處理登入時将使用者的ip儲存起來,這樣就可以在以後的請求中随時驗證後繼請求的ip是否和初始ip相同。儲存這個敏感ip的最佳場所當然是UserData啦,而驗證的時機則是在AuthenticateRequest事件發生時,即Global.aspx.cs中定義的處理此事件的Application_AuthenticateRequest方法中。
上面的示例實際上已經是把使用者ip儲存到了UserData中,下面是驗證的過程。
if(this.Request.IsAuthenticated)
if(((System.Web.Security.FormsIdentity)this.Context.User.Identity).Ticket.UserData !=this.Request.UserHostAddress)
System.Security.Principal.GenericIdentity gi=new System.Security.Principal.GenericIdentity("","");
string[] rolesi={};
System.Security.Principal.GenericPrincipal gpi=new System.Security.Principal.GenericPrincipal(gi,rolesi);
this.Context.User=gpi;
通過給GenericPrincipal空的GenericIdentity和roles使票據失效,這樣将強迫使用者重新登入。為了測試這個方法,可以先把條件改為相等,看效果如何 :)
這個方法也有不足之處,具體為:
1.使用同一代理的使用者将擁有同一個ip,這樣就不能防範此類假冒攻擊了
2.如果使用者使用動态ip,則可能造成正常使用者被我們強行銷毀票據。不過總的來說,這個辦法還是比較可行的。
2.使用安全特性配合Forms驗證進行安全操作。
PrincipalPermissionAttribute可以配合Forms驗證進行基于角色或使用者的安全驗證,該特性不能用于程式集級别。它的作用範圍可以是類或具體的方法。來看一個簡單的示例。
[PrincipalPermission(SecurityAction.Demand,User="Notus")]
public class Test : BasePage
private void Page_Load(object sender, System.EventArgs e)
try
this.sayHello();
this.sayHello2();
catch(Exception ex)
Response.Write(ex.ToString());
private void sayHello()
Response.Write("hello world!");
private void sayHello2()
Response.Write("hello PrincipalPermissionAttribute!");
#region Web 窗體設計器生成的代碼
override protected void OnInit(EventArgs e)
//
// CODEGEN: 該調用是 ASP.NET Web 窗體設計器所必需的。
InitializeComponent();
base.OnInit(e);
/// <summary>
/// 設計器支援所需的方法 - 不要使用代碼編輯器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
this.Load += new System.EventHandler(this.Page_Load);
#endregion
注意這個例子一開始是作用于整個類的,生成後執行,如果目前使用者不是Notus,就會發生異常System.Security.SecurityException,提示對主體權限的請求失敗。反之,則可以順利通路,并輸出兩個hello world!,注意是兩個。現在的安全作用範圍是整個類。
接下來我們改一下特性的作用範圍。将特性聲明移到sayHello2方法上面,重新編譯後運作,就會發現程式在運作到sayHello2方法後抛出了System.Security.SecurityException。這說明現在安全作用範圍縮小到了方法級别。
該特性可以通過設定User和Role來進行基于使用者和角色的安全保護。另外其使用的第一個參數是SecurityAction枚舉,該枚舉設定了具體的保護級别或措施。像我們現在使用的這個Demand,是要求調用堆棧中的所有進階調用方都已被授予了目前權限對象所指定的權限。
下面是msdn給的示例
示例
下面的示例說明可以如何以聲明方式使用 PrincipalPermission 要求目前使用者是 Bob 并且屬于 Supervisor 角色。
[PrincipalPermissionAttribute(SecurityAction.Demand, Name="Bob",
Role="Supervisor")]下面的示例說明如何要求目前使用者的身份是 Bob,與角色成員條件無關。
[PrincipalPermissionAttribute(SecurityAction.Demand, Name="Bob")]
下面的示例說明如何僅要求對使用者進行身份驗證。
[PrincipalPermissionAttribute(SecurityAction.Demand, Authenticated=true)]
再要說一下的是,這裡面的User和Role是可以和Forms驗證內建的,據此,我們可以在一些重要的類或方法中使用PrincipalPermissionAttribute,以将自己的程式武裝到家。
而實際上,該特性的作用遠不止這些,更詳細的資訊可以查閱msdn。
或者:
1.配置Web.Config檔案
設定為Form認證:
<forms name="oursnet" loginUrl="login.aspx" timeout="10" />
其中:
name: 決定驗證使用者時所使用的Cookie名稱
loginurl: 在使用者沒有登入是,被重定向的頁面
timeout: 逾時時間,機關為分鐘
不允許匿名登入
<!-- 允許所有使用者 -->
<deny users="?" />
<!-- <allow users="[逗号分隔的使用者清單]"
roles="[逗号分隔的角色清單]"/>
<deny users="[逗号分隔的使用者清單]"
-->
users: 表示禁止通路資源的使用者清單,使用通配符“?“拒絕匿名使用者通路,使用"*",則表示拒絕所有的使用者通路.
2.添加登入成功的代碼:
Session.Contents["UserName"]=txtUser.Text;
FormsAuthentication.RedirectFromLoginPage(txtUser.Text,false);
Response.Redirect("index.aspx");
3.添加退出時的代碼
System.Web.Security.FormsAuthentication.SignOut();
Response.Redirect("login.aspx");