十年河東,十年河西,莫欺少年窮
學無止境,精益求精
本篇探討下基於NetCore_2.1版本的登錄驗證授權機制,
學過MVC的童鞋們都知道,Form驗證中,MVC中采用的是FormTicket的驗證授權機制,那么在NetCore中怎么實現登錄驗證授權呢?
其實我們在Asp.Net Core項目中的認證,也是比較簡單的。現在我們就通過構建一個小項目來一步步來探討,並最終實現NetCore的登錄驗證及授權。
1、新建一個解決方案(NetCoreAuth),該解決方案中包含五個項目,如下:
1.1、項目NetCoreAuth是一個MVC項目,通過VS直接創建即可,我們稱之為:UI層
1.2、項目NetCoreInterface是接口層,我們都知道NetCore通過依賴注入來進行代碼解耦,因此,接口層是必不可少的
1.3、項目NetCoreService是實現接口的服務層,用於依賴注入時,構建和接口的映射關系,也是必不可少的一層
1.4、項目NetCoreModels是一個Model層,一般項目中,用來構建數據DTO,因此,可以說也是必不可少的一層
1.5、項目NetCoreCommon是一個公共方法層,用於存放通用類,通用枚舉,靜態變量等
2、項目構建完畢后,我們先來完善除NetCoreAuth層(UI層)之外的代碼
2.1、項目NetCoreCommon層很簡單,只有一個類,用於作返回值用的,如下:

namespace NetCoreCommon { public class BaseResponse { public BaseResponse() { this.isSuccess = false; this.resultCode = -1; this.resultMessage = "請求失敗..."; } /// <summary> /// 返回信息 /// </summary> public string resultMessage { get; set; } /// <summary> /// 返回編碼 -1 代表失敗 0代表成功 /// </summary> public int resultCode { get; set; } /// <summary> /// 處理是否成功 /// </summary> public bool isSuccess { get; set; } } public class BaseResponse<T> : BaseResponse { public T Data { get; set; } //public List<T> DataList { get; set; } public BaseResponse() { this.isSuccess = false; this.resultCode = -1; this.resultMessage = "請求失敗..."; } public BaseResponse(T data) { this.Data = data; } } public class CommonBaseResponse { #region 重置Response public static BaseResponse<T> SetResponse<T>(T Data, bool bol, string Msg = "", int cord = 0) { BaseResponse<T> response = new BaseResponse<T>(); response.Data = Data; response.isSuccess = bol; response.resultCode = bol == true ? 0 : -1; if (cord != 0) { response.resultCode = cord; } response.resultMessage = bol == true ? "請求成功..." : "請求失敗..."; if (!string.IsNullOrEmpty(Msg)) { response.resultMessage = Msg; } return response; } public static BaseResponse SetResponse(bool bol, string Msg = "", int cord = 0) { BaseResponse response = new BaseResponse(); response.isSuccess = bol; response.resultCode = bol == true ? 0 : -1; if (cord != 0) { response.resultCode = cord; } response.resultMessage = bol == true ? "請求成功..." : "請求失敗..."; if (!string.IsNullOrEmpty(Msg)) { response.resultMessage = Msg; } return response; } #endregion } }
2.2、項目NetCoreInterface層只有一個接口,用於登錄接口,如下:
public interface IAdminService { UserInfoModel CheckAccountAndPassword(string Account,string Password); }
2.3.項目NetCoreService層實現接口層,因此也只有一個登錄方法,如下:
public class AdminService: IAdminService { /// <summary> /// 檢測當前登錄賬戶 驗證登錄 /// </summary> /// <param name="Account"></param> /// <param name="Password"></param> /// <returns></returns> public UserInfoModel CheckAccountAndPassword(string Account,string Password) { if (Account.ToLower() == "chenwolong" && Password == "123456") return new UserInfoModel(); else return null; } }
2.4、項目NetCoreModels層只有一個實體類,用於當用戶登錄成功后,返回當前登錄用戶的基本信息,並賦值給這個實體類。其作用是:
在項目編碼過程中,我們可隨時通過幫助類,讀取當前登錄人的信息,當然,在此案例中,我並沒有實現通過幫助類讀取當前登錄人信息這個功能。~_~
public class UserInfoModel { public string userId { get; set; } = Guid.NewGuid().ToString(); public string userSex { get; set; } = "男"; public string userPhone { get; set; } = "18137070152"; public string userAccount { get; set; } = "chenwolong"; public string userName { get; set; } = "陳卧龍"; public string userCompany { get; set; } = "盟拓軟件(蘇州)有限公司";
public string userRole { get; set; } = "SuperAdmin"; /* 等等其他屬性 */ }
以上便是上述四個項目的代碼實現,是不是很簡單呢?
然而,我們今天探討的重點不是上述代碼,而是基於identity的登錄驗證授權機制
我們仔細剖析登錄驗證授權這六個字,其重點是最后的兩個字:授權。
登錄驗證無非是用戶在登錄頁登錄,服務器結合數據庫進行賬戶密碼校驗,一旦驗證通過,我們怎么對當前用戶進行授權呢?
針對授權我的理解為:用戶通過登錄驗證后,系統分配一個票據給登錄用戶,用戶在一定的時間內,帶着這個票據進行系統訪問,系統根據當前登錄人所帶的票據,判斷該用戶是否有權限訪問相關資源(結合數據庫權限表中分配的資源及相關數據權限功能權限)
那么,授權怎么實現呢?如下:
3、完善項目中的 Startup 類,如下:
3.1、在Startup類ConfigureServices方法中添加服務
#region 在Start.cs類中,添加服務 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { o.Cookie.Name = "_AdminTicketCookie"; o.LoginPath = new PathString("/Account/Login"); o.LogoutPath = new PathString("/Account/LoginOut"); o.AccessDeniedPath = new PathString("/Error/Forbidden"); o.ExpireTimeSpan = TimeSpan.FromHours(4);//4小時后 Ticket過期 }); services.AddTransient<IAdminService, AdminService>(); #endregion
3.2、在Startup類Configure方法中注冊授權認證中間件,如下:
#region 添加授權中間件 app.UseAuthentication();//添加授權認證中間件 #endregion
3.3、整個Startup類如下:

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NetCoreInterface; using NetCoreService; namespace NetCoreAuth { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); #region 在Start.cs類中,添加服務 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { o.Cookie.Name = "_AdminTicketCookie"; o.LoginPath = new PathString("/Account/Login"); o.LogoutPath = new PathString("/Account/LoginOut"); o.AccessDeniedPath = new PathString("/Error/Forbidden"); o.ExpireTimeSpan = TimeSpan.FromHours(4);//4小時后 Ticket過期 }); services.AddTransient<IAdminService, AdminService>(); #endregion services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); #region 添加授權中間件 app.UseAuthentication();//添加授權認證中間件 #endregion app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Account}/{action=Login}/{id?}"); }); } } }
4、相關控制器及視圖代碼
4.1、核心代碼,用於登錄用戶授權,如下:

public class AccountController : Controller { public IAdminService _AdminService; public ILoggerFactory _Logger; public AccountController(IAdminService Service, ILoggerFactory LoggerService) { _AdminService = Service; _Logger = LoggerService; } public IActionResult Login() { return View(); } /// <summary> /// <![CDATA[登陸]]> /// </summary> /// <param name="model"></param> /// <returns></returns> [HttpPost] public async Task<ActionResult> UserLogin(Models.LoginViewModel model) { try { //模型驗證通過后 if (ModelState.IsValid) { var admin = _AdminService.CheckAccountAndPassword("chenwolong","123456"); //驗證用戶名密碼 if (admin != null) { /*絕對過期時間可以設置為ExpiresUtc。 若要創建持久性 cookie, IsPersistent還必須設置。 否則,cookie 是使用基於會話的生存期創建的,並且可能會在它所包含的身份驗證票證之前或之后過期。 設置ExpiresUtc后,它將覆蓋的ExpireTimeSpan選項的CookieAuthenticationOptions值(如果已設置)。 IsPersistent =false,//關閉瀏覽器 Cookies自動清除 IsPersistent =true,//關閉瀏覽器 Cookies不會自動清除 到設置的時間后,才會自動清除 */ var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);//一定要聲明AuthenticationScheme identity.AddClaim(new Claim(ClaimTypes.Name, admin.userAccount)); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, admin.userId)); identity.AddClaim(new Claim(ClaimTypes.Role, admin.userRole)); identity.AddClaim(new Claim(ClaimTypes.Actor, admin.userName)); await HttpContext.SignInAsync(identity.AuthenticationType, new ClaimsPrincipal(identity), new AuthenticationProperties { IsPersistent =false,//關閉瀏覽器 Cookies自動清除 RedirectUri = "/Home/Index", ExpiresUtc = new System.DateTimeOffset(dateTime: DateTime.Now.AddHours(6)), }); } else { await HttpContext.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme); ModelState.AddModelError("", "用戶名或密碼錯誤!"); } } else { var ErrData = ModelState.Where(x => x.Value.Errors.Count > 0) .Select(x => new { Name = x.Key, Message = x.Value.Errors?.FirstOrDefault().ErrorMessage }).ToList(); string ErrorMessage = string.Empty; foreach (var item in ErrData) { if (ErrData.IndexOf(item) == ErrData.Count - 1) { ErrorMessage += item.Message + "。"; } else { ErrorMessage += item.Message + ","; } } return Json(CommonBaseResponse.SetResponse(false, ErrorMessage)); } } catch (Exception ex) { throw ex; } return Json(CommonBaseResponse.SetResponse(true, "登錄成功")); } public IActionResult LoginOut() { return View(); } }
4.2、登錄方法中引用一個ViewModel,如下:

namespace NetCoreAuth.Models { public class LoginViewModel { [Required(AllowEmptyStrings =false,ErrorMessage ="用戶名為必填項")] [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$",ErrorMessage ="用戶名必須包含字母大小寫")] public string Account { get; set; } [Required(AllowEmptyStrings = false, ErrorMessage = "用戶登錄密碼為必填項")] [MinLength(6,ErrorMessage ="密碼最小長度為6位")] public string Password { get; set; } } }
4.3、登錄頁面如下:
頁面只是簡單的做了實現,HTML編碼如下:

@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Login</title> <script src="~/lib/jquery/dist/jquery.js"></script> <script type="text/javascript"> function Login() { var Account = $("#Account").val(); var Password = $("#Password").val(); var UserData = { Account: Account, Password: Password }; $.post("/Account/UserLogin", UserData, function (result) { if (result.isSuccess) { window.location.href = "/home/index"; } else { alert(result.resultMessage) } }); } </script> </head> <body> <div>用戶名:<input id="Account" name="Account" type="text" /></div> <div>密碼:<input id="Password" name="Password" type="text" /></div> <div> <input id="Button1" type="button" onclick="Login()" value="登錄" /> </div> </body> </html>
通過HTML編碼中的JS方法,我們知道,用戶登錄授權后,會跳轉至/Home/Index
那么,在Home/Index頁面中,我們如何判斷當前用戶是否通過登錄驗證授權?
如下:
繼承自BaseController,代碼如下:
public class BaseController : Controller { public override void OnActionExecuting(ActionExecutingContext context) { var data = context.HttpContext.User; //沒有通過登錄驗證授權 if (data.Identity.IsAuthenticated == false) { Response.Redirect("/Account/Login"); } } }
方法 OnActionExecuting 在基類視圖方法運行之前執行,因此,在基類視圖呈現之前,我們有必須判斷登錄人攜帶的憑據。
當然,高手也可以通過過濾器或者自定義中間件來判斷攜帶的憑據是否合法。
@天才卧龍的博客