Asp.Net MVC 身份驗證-Forms

JoeSnail發表於2018-01-09

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,就可以獲取到跳轉地址。

Form特性的詳細說明

此時當未登入的使用者訪問有[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

如:

Asp.Net MVC 身份驗證-Forms

該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;
    }
}

這個方法的核心是:

  1. 在登入時,建立自定義的FormsAuthenticationTicket物件,它包含了使用者資訊。
  2. 加密FormsAuthenticationTicket物件。
  3. 建立登入Cookie,它將包含FormsAuthenticationTicket物件加密後的結果。
  4. 在管線的早期階段,讀取登入Cookie,如果有,則解密。
  5. 從解密後的FormsAuthenticationTicket物件中還原我們儲存的使用者資訊。
  6. 設定HttpContext.User為我們自定義的物件。

程式碼

如有不對,請多多指教。


參考:


相關文章