2013年10月22日 星期二

Asp.net MVC 實作登入驗證(1)

先廢話一下
離上次寫MVC文章剛好一年了
這一年裡基本上我都沒機會去使用MVC
剛好這次公司有點數所以去上了課
自己又投資自己去上保哥的MVC5課程
剛好兩邊教法不一樣有些重點也不一樣
趁著還熟著時順手向同事要了個已經快開發成的案子
當然第一次寫要上線應該很難
接下來幾天會針對實作及遇到的問題做整理

一開始我想剛學完基礎的人
基本上跟我一樣不知如何下手
隨便要個舊專案或是拿個不是很重要的案子來寫
首先應該會從會員開始吧
但千萬別從新增會員開始
因為這樣就有點難了
我是以VS2013來開發目前是RTM
1.新增一個專案 > .net版本選4.5 > 選MVC > 建好了網站 > 刪除一些不必要的原件(Controllers Views Models)裡都刪了 只留View裡的Shared
2013範本是Bootstrap 3.0,jQuery 1.10.2。
2.開發分成Code First、DB First、Model First因個人習慣DB First所以先來建資料庫,先求簡單只有三個欄位,ID、登入帳號、密碼,但有一個重點記得設PK,EF必要的就是PK,這次實做簡單登入 所以直街先手動新增一筆假一料
3.建立一個ADO.NET 實體模型資料庫 > 由資料庫產生 > 新增連接 > 依提示操作設定連線資訊 > 測試連接看能不能成功 > 完成後記得build一下
4.在Controller新增一個AccountController 在新增一個View /Account/Login.cshtml
因為在微軟內建的驗證中有一個已經幫我們做好驗正了
 app.UseCookieAuthentication(new CookieAuthenticationOptions
 {
         AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
         LoginPath = new PathString("/Account/Login")
 });
5.新增一個ViewModels資料夾來存放自己定義的model 加入
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
定義Model
    public class Login 
    {
        [DisplayName("登入帳號")]
        [Required(ErrorMessage = "請輸入登入帳號")]
        [StringLength(40, ErrorMessage = "登入帳號最多20個字")]
        public string AccountName { get; set; }

        [DisplayName("登入密碼")]
        [Required(ErrorMessage = "請輸入登入密碼")]
        [StringLength(40, ErrorMessage = "密碼最多20個字")]
        [DataType(DataType.Password)]
        public string  Password { get; set; }
    }
Login的畫面
2013-10-30更新
                @using (Html.BeginForm("Login", "Login", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post))
                {
                    @Html.AntiForgeryToken()
                    @*@Html.ValidationSummary()*@

                    

後台登入

@Html.LabelFor(model => model.AccountName, new { @class = "control-label" })
@Html.TextBoxFor(model => model.AccountName, new { @class = "form-control", placeholder = "請輸入登入帳號" }) @Html.ValidationMessageFor(model => model.AccountName, null, new { @class = "help-inline" })
@Html.LabelFor(model => model.Password, new { @class = "control-label" })
@Html.PasswordFor(model => model.Password, new { @class = "form-control", placeholder = "請輸入登入密碼" }) @Html.ValidationMessageFor(model => model.Password, null, new { @class = "help-inline" })
@TempData["Error"]
}
登入的Action
2013-10-30更新
      [HttpPost]
        [ValidateAntiForgeryToken]     
        public ActionResult Login(Login form)
        {
            if (ModelState.IsValid)
            {               
                //驗證資料庫登入
                //這邊請使用自行驗證摟
                string Sql = " AccountName == @0 and Password == @1 and IsUsed == @2";
                var query = (from u in db.Account.Where(Sql, form.AccountName, form.Password, "true")
                             select u).ToList();

                if (query.Count() != 1)
                {                   
                    ModelState.AddModelError(string.Empty, "帳號或密碼錯誤登入失敗");
                    return View("Index",form);
                }

                try
                {
                    query[0].LoginIP =PublicFunction.GetIpAddress();
                    query[0].LoginDate = DateTime.Now;
                    db.SaveChanges();
                }
                catch (Exception ex)
                {
                    ModelState.AddModelError(string.Empty, ex.Message.ToString());
                    return View("Index", form);
                }
              

                bool isPersistent = false;//如果票證將存放於持續性 Cookie 中 (跨瀏覽器工作階段儲存),則為 true,否則為 false。 如果票證是存放於 URL 中,則忽略這個值。
                string userData = "";//可放使用者自訂的內容
                string mAccountID = query[0].AccountID.ToString();
               
                //寫cookie
                //使用 Cookie 名稱、版本、到期日、核發日期、永續性和使用者特定的資料,初始化 FormsAuthenticationTicket 類別的新執行個體。 此 Cookie 路徑設定為在應用程式的組態檔中建立的預設值。
                //使用 Cookie 名稱、版本、目錄路徑、核發日期、到期日期、永續性和使用者定義的資料,初始化 FormsAuthenticationTicket 類別的新執行個體。
                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
                  mAccountID,//使用者ID
                  DateTime.Now,//核發日期
                  DateTime.Now.AddMinutes(1800),//到期日期 30分鐘 
                  isPersistent,//永續性
                  userData,//使用者定義的資料
                  FormsAuthentication.FormsCookiePath);
              
                string encTicket = FormsAuthentication.Encrypt(ticket);
                Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

                //HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
                //cookie.Expires = ticket.Expiration;
                //Response.Cookies.Add(cookie);

                //FormsAuthentication.RedirectFromLoginPage(strUsername, isPersistent);

                if (form.ReturnUrl != null)
                {
                    return Redirect(form.ReturnUrl.ToString());
                }
                else
                {
                    return RedirectToAction("Index", "Admin");
                }
                
            }
            //return RedirectToAction("Index", "Login", null);
            return View("Index", form);
        }

最後再需要驗證的Class上加[Authorize] 雖然大部份程式碼都是copy來的
但也花了一下午才了解如何登入
看來還真有的學呢

2013-10-26補充 請注意Web.Config裡的設定

修改成
 
      
    

2013-10-30更新
在login頁多加一個returnUrl
這樣使用者登入後可直接到他需要的頁面
上面的View跟Controller都需更新
   public ActionResult Index(string returnUrl)
        {            
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }   

2013-11-13更新
MVC5使用範本新增如果加了authentication mode="Forms"
此時自訂的驗證會不正常
修改
public override void ExecuteResult(ControllerContext context)
            {               
                var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
改為
public override void ExecuteResult(ControllerContext context)
            {
               //多下列這行
                context.RequestContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
                var properties = new AuthenticationProperties() { RedirectUri = RedirectUri };
                if (UserId != null)
                {
                    properties.Dictionary[XsrfKey] = UserId;
                }
                context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
            }
參考網址
點我前往
點我前往
點我前往
點我前往
點我前往
點我前往