Asp.net authentication mode="Forms"
淘到的Form驗證過程:(如果所有頁面繼承了同一個判斷是否登入的類,路徑的判斷是個問題,檔案所處的位置可能不同,有的是二級選單,有的三級。。。還有的是通過Request.Url.LocalPath來獲取的,xp/iis5.1下如果設定虛擬目錄,取到的路徑是http://localhost/應用程式名/地址,這裡它比win2003/iis6下多了一個應用程式名,iis設定和路徑的問題,可能導致下面的表單認證達不到正常的效果)
如何運用 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>
這裡沒什麼好說的,只要拷貝過去就行。雖說如此,但還是有人會弄錯,如下:
<authentication mode="Forms">
<forms loginUrl="Login.aspx" name=".APSX"></forms>
<deny users="?"></deny>
</authentication>
若要問是誰把 <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)
{
if(this.Txt_UserName.Text=="Admin" && this.Txt_Password.Text=="123456")
{
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。內容如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<authorization>
<allow users="Admin"/>
<deny users="*"/>
</authorization>
</system.web>
</configuration>
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>
</authentication>
<authorization>
<deny users="?" />
</authorization>
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認證:
<authentication mode="Forms">
<forms name="oursnet" loginUrl="login.aspx" timeout="10" />
</authentication>
其中:
name: 決定驗證使用者時所使用的Cookie名稱
loginurl: 在使用者沒有登入是,被重定向的頁面
timeout: 超時時間,單位為分鐘
不允許匿名登入
<authorization>
<!-- 允許所有使用者 -->
<deny users="?" />
<!-- <allow users="[逗號分隔的使用者列表]"
roles="[逗號分隔的角色列表]"/>
<deny users="[逗號分隔的使用者列表]"
roles="[逗號分隔的角色列表]"/>
-->
</authorization>
其中:
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");