我們開發web系統,用戶身份驗證是最常見不過的。最簡單的辦法就是定一個基類,基類裏面有判斷Cookie或Session是否存在,然後決定是否跳轉。今天就利用MVC的特性來一個不一樣的驗證方式。
- public class CustomAuthorizeAttribute : AuthorizeAttribute
- {
- protected override bool AuthorizeCore(HttpContextBase httpContext)
- {
- UserIdentity user = WebUtility.GetIdentity(httpContext);
- if (user != null)
- {
- httpContext.User = new UserPrincipal(WebUtility.GetIdentity(httpContext));
- }
- return user != null;
- }
- }
這是自定義的驗證機制,重寫了系統自帶的驗證核心邏輯,下面是系統自帶的邏輯:
- protected virtual bool AuthorizeCore(HttpContextBase httpContext)
- {
- if (httpContext == null)
- {
- throw new ArgumentNullException("httpContext");
- }
- IPrincipal user = httpContext.User;
- return user.Identity.IsAuthenticated && (this._usersSplit.Length <= 0 || this._usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) && (this._rolesSplit.Length <= 0 || this._rolesSplit.Any(new Func<string, bool>(user.IsInRole)));
- }
可以看出,驗證都是用httpContext.User屬性來判斷,所以我們對登錄、退出和獲取用戶信息都通過httpContext.User屬性就行了。從MVC的示例中我們也可以發現確實如此,看看MVC3的Views裏的_LogOnPartial.cshtml的代碼:
- @if(Request.IsAuthenticated) {
- <text>Welcome <strong>@User.Identity.Name</strong>!
- [ @Html.ActionLink("Log Off", "LogOff", "Account") ]</text>
- }
- else {
- @:[ @Html.ActionLink("Log On", "LogOn", "Account") ]
- }
那麼現在再看我們剛纔重寫的驗證邏輯,就大致明白了。首先通過一個外部的邏輯獲取User,如果沒有User說明沒有登錄,否則就給User賦值,標記登錄了。
再看我們是如何獲取這個User的
public static UserIdentity GetIdentity(HttpContextBase context)
{
var user = GetUser(context);
return new UserIdentity(user);
}
public static User GetUser(HttpContextBase httpContext)
{
var user = httpContext.Request.Cookies[COOKIE_NAME_KEY]; //明文 比如用的userId
var info = httpContext.Request.Cookies[COOKIE_INFO_KEY]; //密文
if (user == null || info == null || !info.Value.DESDecrypt(DES_KEY).Contains(user.Value)) //解密後不一致,說明無效
{
return null;
}
return new User{UserID = int.parse(user.value)};
}
這就很易懂了,也是通過的cookie對用戶資料做的存儲。
httpContext.User是IPrincipal類型,所以我們要自定義一個UserPrincipal類:
public class UserPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
public UserPrincipal(IIdentity identity)
{
this.Identity = identity;
}
public bool IsInRole(string role)
{
throw new NotSupportedException();
}
}
似乎UserPrincipal並沒有什麼內容,而真證的數據存在Identity屬性裏,所以還需要定義一個UserIdentity類,來滿足UserPrincipal。所以從最上面的代碼可以看出WebUtility.GetIdentity(httpContext)返回的就是一個UserIdentity實例。
- public class UserIdentity : IIdentity
- {
- public UserIdentity(User user)//此User就是從Cookie解密出來實例化的User
- {
- User = user;
- }
- public User User { get; private set; }
- public string Name
- {
- get { return User == null ? string.Empty : User.UserName; }
- }
- public string AuthenticationType
- {
- get { return "maddemon"; }
- }
- public bool IsAuthenticated
- {
- get { return User != null; }
- }
- }
好了,整個驗證核心需要的東西我們都準備好了,還差一個最重要的東西就是我們登錄的時候需要寫cookie,退出的時候需要清除cookie。
- public static void SetAuthorizeCookie(HttpContextBase httpContext, User user)
- {
- httpContext.Response.SetCookie(new HttpCookie(COOKIE_NAME_KEY, user.UserName));
- httpContext.Response.SetCookie(new HttpCookie(COOKIE_INFO_KEY, (user.UserName + "|" + user.Role).DESEncrypt(DES_KEY)));
- }
- public static void RemoveAuthorizeCookie(HttpContextBase httpContext)
- {
- httpContext.Response.SetCookie(new HttpCookie(COOKIE_NAME_KEY, null) { Expires = DateTime.Now.AddDays(-1) });
- httpContext.Response.SetCookie(new HttpCookie(COOKIE_INFO_KEY, null) { Expires = DateTime.Now.AddDays(-1) });
- }
這樣一套下來,需要登錄的Controller或Action,我們只需要加上一個[CustomAuthorize]屬性就可以,如果沒有登錄就會自動跳轉到config裏配置的登錄頁。是不是很不方便啊? 方便嗎?不方便嗎? 方便嗎?不方便嗎? 方便嗎?不方便嗎?是不是啊 哈哈
- <authentication mode="Forms">
- <forms loginUrl="~/Account/LogOn" timeout="2880" />
- </authentication>