Asp.Net MVC 身份驗證-Forms
在MVC中對於需要登錄才可以訪問的頁面,只需要在對應的Controller或Action上添加特性[Authorize]
就可以限制非登錄用戶訪問該頁面。那么如果實現登錄?
Form登錄
應用程序確認用戶狀態
HTTP協議是無狀態的。所以上一次請求和下一次請求並不能相互關聯起來,就是說這些請求並不能確定是哪個用戶和用戶的狀態。但是對於登錄來說,我們就需要准確的知道用戶的狀態及是哪個用戶。
通常有兩種情況來記錄用戶狀態。
-
一種在服務端通過Session來標識。
-
一種通過Cookie在客戶端標識用戶。(用戶每次請求應用程序時都會攜帶該Cookie。)
Form登錄實現
Forms 身份驗證將身份驗證標記保留在 Cookie 或頁的 URL 中。Forms 身份驗證通過 FormsAuthenticationModule 類參與到 ASP.NET 頁面的生命周期中。可以通過 FormsAuthentication 類訪問 Forms 身份驗證信息和功能。
步驟一
在Web.Config
配置文件中指定驗證的方式為Form
,並設置跳轉的登錄地址和Cookie的名稱,及超時時間等。
<system.web>
<authentication mode="Forms">
<forms loginUrl="/Home/Login" timeout="120" cookieless="UseCookies" name="LoginCookieName"></forms>
</authentication>
</system.web>
設置該配置文件,並不需要特意給Action傳遞returnUrl,就可以獲取到跳轉地址。
此時當未登錄的用戶訪問有[Authorize]特性的Action操作時,會被跳轉到Login頁面,同時Login頁面的URL后面會添加一個加密的ReturnUrl地址,該地址指向之前訪問的有[Authorize]特性的Action地址。
步驟二
之前提到Form認證其實就是生成了一個Cookie,存放到用戶的瀏覽器中。通過FormAuthenication.SetAuthCookie(userName,true);
設置驗證登錄的Cookie。再通過頁面跳轉將Cookie響應給客戶端。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel vm,string returnUrl)
{
if (ModelState.IsValid)
{
//用戶名,密碼驗證
FormsAuthentication.SetAuthCookie(vm.UserName, true); //設置Form驗證Cookie,后一個 參數為是否創建持久Cookie。及true為可以在用戶瀏覽器上保存的。false為不在瀏覽器上保存。
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Detail");
}
else
return View(vm);
}
此時當我們登錄以后會在瀏覽器中生成一個跟配置文件中名稱相同的Cookie
如:
該Cookie就是我們已經登錄,通過Form驗證的憑證。
此時我們就可以訪問那些需要登錄才能訪問的頁面。
注銷
刪除對應的Cookie即可實現注銷,代碼如下:
[Authorize]
public ActionResult LogOut()
{
FormsAuthentication.SignOut();//通過Forms驗證來刪除Cookie
return View();
}
角色添加
有些頁面可能只允許特定用戶才能訪問,在MVC中可以通過[Authorize(Roles="VIP")]
設置Action或Controller,表示只有角色為VIP的用戶才可以訪問該頁面。
如:只有登錄且用戶角色為VIP的才可以訪問這個頁面。
[Authorize(Roles = "VIP")]
public ActionResult VIP()
{
return View();
}
同時
需要在設置Form驗證憑據時把用戶角色添加到Cookie。
如:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginViewModel vm, string returnUrl)
{
if (ModelState.IsValid)
{
//用戶名,密碼驗證
//FormsAuthentication.SetAuthCookie(vm.UserName, true); //設置Form驗證Cookie,后一個 參數為是否創建持久Cookie。及true為可以在用戶瀏覽器上保存的。false為不在瀏覽器上保存。
//if (Url.IsLocalUrl(returnUrl))
//{
// return Redirect(returnUrl);
//}
//return RedirectToAction("Detail");
vm.Role = "VIP";
var authTicket = new FormsAuthenticationTicket(
1, // 版本
vm.UserName, // 用戶名稱
DateTime.Now, // 創建日期
DateTime.Now.AddMinutes(20), // 過期時間
vm.Remember, // 是否記住
vm.Role // 用戶角色
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.HttpOnly = true;//客戶端腳本不能訪問
authCookie.Secure = FormsAuthentication.RequireSSL;//是否僅用https傳遞cookie
authCookie.Domain = FormsAuthentication.CookieDomain;//與cookie關聯的域
authCookie.Path = FormsAuthentication.FormsCookiePath;//cookie關聯的虛擬路徑
Response.Cookies.Add(authCookie);
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
return RedirectToAction("Detail");
}
else
return View(vm);
}
在Global.asax.cs文件中重寫Application_AuthenticateRequest
事件
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
//獲取Cookie
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
return;
FormsAuthenticationTicket authTicket;
try
{
//解析Cookie
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
// 解析權限
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null)
//把權限賦值給當前用戶
Context.User = new GenericPrincipal(Context.User.Identity, roles);
}
至此最簡單的Form身份驗證實現了。但是該Cookie只包含了用戶名,沒有其他信息。如果要包含其他信息,可以通過擴展用戶的身份標識(HttpContext.User實例)來實現。
HttpContext.User定義如下
通過User屬性可以訪問Iprincipal接口的屬性和方法。
//獲取或設置當前 HTTP 請求的安全信息。
public IPrincipal User
{
get;
[SecurityPermissionAttribute(SecurityAction.Demand, ControlPrincipal = true)]
set;
}
所以擴展用戶身份標識可以通過實現IPrincipal接口。
如:
public class MyFormsPrincipal<TUserData> : IPrincipal where TUserData : class, new()
{
private IIdentity _identity;
private TUserData _userData;
public MyFormsPrincipal(FormsAuthenticationTicket ticket, TUserData userData)
{
if( ticket == null )
throw new ArgumentNullException("ticket");
if( userData == null )
throw new ArgumentNullException("userData");
_identity = new FormsIdentity(ticket);//Forms身份驗證
_userData = userData;
}
public TUserData UserData
{
get { return _userData; }
}
public IIdentity Identity
{
get { return _identity; }
}
public bool IsInRole(string role)
{
return false;
}
}
這個方法的核心是:
- 在登錄時,創建自定義的FormsAuthenticationTicket對象,它包含了用戶信息。
- 加密FormsAuthenticationTicket對象。
- 創建登錄Cookie,它將包含FormsAuthenticationTicket對象加密后的結果。
- 在管線的早期階段,讀取登錄Cookie,如果有,則解密。
- 從解密后的FormsAuthenticationTicket對象中還原我們保存的用戶信息。
- 設置HttpContext.User為我們自定義的對象。
如有不對,請多多指教。
參考: