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 Form設計器產生的程式碼
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: 該調用是 ASP.NET Web Form設計器所必需的。
//
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。