說明:本文示例使用的VS2017和MVC5。 系統無論大小、牛逼或屌絲,一般都離不開註冊、登錄。那麼接下來我們就來分析下用戶身份認證。 簡單實現登錄、註銷 以前在學習.net的時候不知道什麼Forms身份認證,直接用session實現登錄,效果也蠻好嘛。而且用戶信息存在服務端,安全。 前端代碼: 後 ...
說明:本文示例使用的VS2017和MVC5。
系統無論大小、牛逼或屌絲,一般都離不開註冊、登錄。那麼接下來我們就來分析下用戶身份認證。
簡單實現登錄、註銷
以前在學習.net的時候不知道什麼Forms身份認證,直接用session實現登錄,效果也蠻好嘛。而且用戶信息存在服務端,安全。
前端代碼:
@if (string.IsNullOrWhiteSpace(ViewBag.UserName))
{
<form action="/home/login1">
<input type="text" name="userName" />
<input type="submit" value="登錄" />
</form>
}
else
{
<form action="/home/logout1">
<div>當前用戶已登錄,登錄名:@ViewBag.UserName</div>
<input type="submit" value="退出" />
</form>
}
後臺代碼:
public ActionResult Index()
{
ViewBag.UserName = Session["userName"]?.ToString();
return View();
}
public void Login1(string userName)
{
if (!string.IsNullOrWhiteSpace(userName)) //為了方便演示,就不做真的驗證了
Session["userName"] = userName;
else
Session["userName"] = null;
Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原來頁面
}
public void Logout1()
{
Session["userName"] = null;
Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原來頁面
}
是不是,簡單明瞭。想要自己擴展或是定製什麼功能都非常好用。不過我們需要維護session。比如系統重新發佈,或者iis被自動重啟。就會出現session丟失的情況。也就是用戶會莫名其妙提升需要重新登錄。體驗非常不好。(這裡先不討論session服務和資料庫的情況)。既然微軟有一套成熟的許可權管理我們為什麼不用呢?
Forms認證登錄、註銷
首先在web.config里開啟Forms身份認證:
<system.web>
<authentication mode="Forms"></authentication>
後臺代碼:
public void Login2(string userName)
{
if (!string.IsNullOrWhiteSpace(userName)) //為了方便演示,就不做真的驗證了
FormsAuthentication.SetAuthCookie(userName, true); //登錄
Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原來頁面
}
public void Logout2()
{
FormsAuthentication.SignOut();//登出
Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原來頁面
}
前臺代碼:
@if (!Request.IsAuthenticated)
{
<form action="/home/login2">
<input type="text" name="userName" />
<input type="submit" value="登錄" />
</form>
}
else
{
<form action="/home/logout2">
<div>當前用戶已登錄,登錄名:@Context.User.Identity.Name</div>
<input type="submit" value="退出" />
</form>
}
如此幾句代碼就實現了我們的登錄和註銷。和我們自己用session管理登錄不同。Forms身份認證是直接把信息存cookie到瀏覽器的。通過SetAuthCookie這個方法名也可以看出來。不過Cookie信息經過了加密。
這裡有必要說明session和cookie的關係。當我們利用session來維持用戶狀態的時候,其實也用到了cookie。
然而Forms身份認證僅僅只是把信息存了cookie,而沒有在服務端維護一個對應的session。
不信你可以測試。可以用兩種方式都登錄,然後清除session就可以測出來了。(怎麼清session?重啟iis,或者修改下後臺代碼在重新編譯訪問)
【說明】用戶認證為什麼要存cookie?因為HTTP是一個無狀態的協議。對於伺服器來說,每次請求都是一樣的。所以,只能通過每次請求帶的cookie來識別用戶了。(暫時不考慮其他方式)
自定義的身份認證標識
上面使用的登錄很簡單,但實際情況往往很複雜。明顯正常業務需要存的用戶信息會要更多。那麼我們是否可以擴展身份標識呢?答案是肯定的。
後臺代碼:
public void Login3(string userName)
{
if (!string.IsNullOrWhiteSpace(userName)) //為了方便演示,就不做真的驗證了
{
UserInfo user = new UserInfo()
{
Name = userName,
LoginTime = DateTime.Now
};
//1、序列化要保存的用戶信息
var data = JsonConvert.SerializeObject(user);
//2、創建一個FormsAuthenticationTicket,它包含登錄名以及額外的用戶數據。
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddDays(1), true, data);
//3、加密保存
string cookieValue = FormsAuthentication.Encrypt(ticket);
// 4. 根據加密結果創建登錄Cookie
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue);
cookie.HttpOnly = true;
cookie.Secure = FormsAuthentication.RequireSSL;
cookie.Domain = FormsAuthentication.CookieDomain;
cookie.Path = FormsAuthentication.FormsCookiePath;
// 5. 寫登錄Cookie
Response.Cookies.Remove(cookie.Name);
Response.Cookies.Add(cookie);
}
Response.Redirect(Request.UrlReferrer.LocalPath);//重定向到原來頁面
}
然後在Global.asax的Application_AuthenticateRequest方法:
protected void Application_AuthenticateRequest()
{
GetUserInfo();
}
//通過coolie解密 讀取用戶信息到 HttpContext.Current.User
public void GetUserInfo()
{
// 1. 讀登錄Cookie
HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
try
{
UserInfo userData = null;
// 2. 解密Cookie值,獲取FormsAuthenticationTicket對象
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket != null && string.IsNullOrEmpty(ticket.UserData) == false)
// 3. 還原用戶數據
userData = JsonConvert.DeserializeObject<UserInfo>(ticket.UserData);
if (ticket != null && userData != null)
// 4. 構造我們的MyFormsPrincipal實例,重新給context.User賦值。
HttpContext.Current.User = new MyFormsPrincipal<UserInfo>(ticket, userData);
}
catch { /* 有異常也不要拋出,防止攻擊者試探。 */ }
}
前端代碼:
@{
MyFormsPrincipal<UserInfo> user = Context.User as MyFormsPrincipal<UserInfo>;
if (user == null)
{
<form action="/home/login3">
<input type="text" name="userName" />
<input type="submit" value="登錄" />
</form>
}
else
{
<form action="/home/logout2">
<div>當前用戶已登錄,登錄名:@Context.User.Identity.Name</div>
<div>當前用戶已登錄,登錄時間:@user.UserData.LoginTime</div>
<input type="submit" value="退出" />
</form>
}
}
其實整個過程和FormsAuthentication.SetAuthCookie(userName, true); //登錄
是等效的。只是我們通過擴展,存了我們想要存儲的數據。
過程也比較簡單:
- 構造要存儲的數據
- 序列化
- 把序列化信息放入FormsAuthenticationTicket對象
- 通過FormsAuthentication.Encrypt加密對象
- 發送cookie到瀏覽器
這裡稍微複雜點的地方就是解密然後給User賦值HttpContext.Current.User = new MyFormsPrincipal<UserInfo>(ticket, userData);
。
MyFormsPrincipal需要實現介面MyFormsPrincipal
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);
_userData = userData;
}
public TUserData UserData
{
get { return _userData; }
}
public IIdentity Identity
{
get { return _identity; }
}
public bool IsInRole(string role)//這裡暫時不實現
{
return false;
}
}
倒也沒有什麼特別,就是實例化的時候傳入票據和自定義數據就好了。
授權
有了登錄一般都離不開授權。微軟的東西好就好在,一般都是成套成套的。
[Authorize]
public ActionResult LoginOk()
{
return View();
}
直接給Action添加一個Authorize特性就好了,這人就會自動檢查是否登錄。如果沒有登錄自動跳轉到登錄頁面。登錄頁面的設置還是在web.config裡面
<system.web>
<authentication mode="Forms" >
<forms loginUrl="/home/index"></forms>
這種簡單的授權驗證明顯是不夠的。很多時候某些頁面只有某些人才能訪問。比如VIP。那麼我們又要擴展了。
//繼承 AuthorizeAttribute
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.User.Identity.Name != "農碼一生")
{
filterContext.HttpContext.Response.Write("您不是vip用戶,不能訪問機密數據");
filterContext.HttpContext.Response.End();
return;
}
base.OnAuthorization(filterContext);
}
}
[MyAuthorize]
public ActionResult LoginVIP()
{
return View();
}
是的,就是這麼簡單。說了這麼多,來張效果圖吧:
推薦閱讀:
- http://www.cnblogs.com/fish-li/archive/2012/04/15/2450571.html
Demo: - https://github.com/zhaopeiym/BlogDemoCode/tree/master/許可權管理/1-Forms身份認證