本片內容使用到ids4+ids4.Entityframework持久化表單,以及core的identity相關表的一併持久化,而後就是登陸認證,認證使用email發送郵件的方式。因此這裏涉及到四塊內容,1.ids4的集成,2.ids4+core identity的相關默認表的持久化,以及在遷移庫、表的過程當中初始化相關數據(用戶數據);3.登陸認證 4.mailkit郵件發送(見上篇),框架是按照ddd搭建的,該篇內容只是ddd中的一個支撐域的東西(一個子域),用於統一認證和受權的,內容比較簡單也比較少,可是框架沒徹底寫好,因此不放出來了。html
1.首先須要建立一個.net core項目,而後git
2.選擇使用登陸認證(這也就涉及到了identity的東西了),而後建立好項目以後github
3.右擊解決方案,打開解決方案文件夾,sql
4.按住shift,而後右擊鼠標,點擊 powerShell,輸入如下內容回車:iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/master/getmaster.ps1'))
shell
此時能夠看到項目中生成了 quictstart.ui的相關內容,此內容是ids4的參考ui,相對於本身寫省了很多事情了,詳見:https://github.com/IdentityServer/IdentityServer4.Quickstart.UI 或者本身直接百度 identityserver4 ui就能夠搜到。api
1.首先,咱們在使用ids4的時候,須要添加兩個遷移文件,詳見這裏:http://docs.identityserver.io/en/latest/quickstarts/7_entity_framework.htmlcookie
dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
以上兩航依舊是在上面的步驟中的 powershell執行的。app
2.而後是ids的相關配置(startup.cs中),configureServices方法框架
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; }); //services.AddDbContext<ApplicationDbContext>(options => // options.UseSqlServer( // Configuration.GetConnectionString("DefaultConnection"))); //services.AddDefaultIdentity<IdentityUser>() // .AddDefaultUI(UIFramework.Bootstrap4) // .AddEntityFrameworkStores<ApplicationDbContext>(); //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); var connectionString = Configuration.GetConnectionString("DefaultConnection"); var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); services.AddIdentity<ApplicationUser/*IdentityUser*/, ApplicationRole>(options => { // Password settings options.Password.RequireDigit = false; options.Password.RequiredLength = 6; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; options.Password.RequiredUniqueChars = 2; // Lockout settings options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = true; // Signin settings options.SignIn.RequireConfirmedEmail = false; options.SignIn.RequireConfirmedPhoneNumber = false; // User settings options.User.RequireUniqueEmail = false; }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //添加ids4配置 services.AddIdentityServer() .AddDeveloperSigningCredential() //.AddSigningCredential(new X509Certificate2(@"D:\WORKSPACE\CSHARP_CORE\esoftor-ddd\src\ESoftor.Authorization.Server\bin\Debug\netcoreapp2.2\tempkey.rsa")) .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); options.EnableTokenCleanup = true; options.TokenCleanupInterval = 30; }) .AddAspNetIdentity<ApplicationUser/*IdentityUser*/>();
configure方法中async
app.UseCookiePolicy(); //app.UseAuthentication(); app.UseIdentityServer();//ids4的UseIdentityServer包含了UseAuthentication,因此不須要上面的UseAuthentication
3.最重要的一步,由於添加完上面的東西以後會報錯,呵呵,nuget添加如下相關的 程序集:
IdentityServer4
IdentityServer4.AccessTokenValidation
IdentityServer4.AspNetIdentity
IdentityServer4.EntityFramework
由於須要遷移生成庫,因此還須要添加ef core的相關包
Microsoft.EntityFrameworkCore.SqlServer (這裏我是用的mssql)
Microsoft.EntityFrameworkCore.Tools
此時咱們的基礎工做基本完成了,其中涉及到ApplicationUser ApplicationRole,ApplicationUserRole,ApplicationIdentityUserLogin的內容,以下:
// ApplicationIdentityUserLogin.cs public class ApplicationIdentityUserLogin : IdentityUserLogin<Guid> { } // ApplicationRole.cs public class ApplicationRole : IdentityRole<Guid> { /// <summary> /// Gets or sets the UserRoles /// </summary> public ICollection<ApplicationUserRole> UserRoles { get; set; } } // ApplicationUser.cs public class ApplicationUser : IdentityUser<Guid> { /// <summary> /// Gets or sets the UserRoles /// </summary> public ICollection<ApplicationUserRole> UserRoles { get; set; } } // ApplicationUserRole public class ApplicationUserRole : IdentityUserRole<Guid> { /// <summary> /// Gets or sets the User /// </summary> public virtual ApplicationUser User { get; set; } /// <summary> /// Gets or sets the Role /// </summary> public virtual ApplicationRole Role { get; set; } }
而後咱們須要針對core 的identity,注意,這裏說的是identity,再單獨生成一個遷移文件,這裏就不說了,無非是 add-migration或者 dotnet ef add migrations
4.依舊在startup.cs中添加遷移用方法,說以前先說下ids4的官網, http://docs.identityserver.io/en/latest/quickstarts/7_entity_framework.html,其中提供的參考代碼就是咱們須要的,可是咱們這裏還須要添加對identity的相關表數據的初始化,也就是咱們上面定義的幾個表ApplicationUser,App....,因此咱們的代碼以下:
private void InitializeDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope()) { //ids4 serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate(); var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>(); context.Database.Migrate(); if (!context.Clients.Any()) { foreach (var client in InMemoryConfiguration.Clients()) { context.Clients.Add(client.ToEntity()); } context.SaveChanges(); } if (!context.IdentityResources.Any()) { foreach (var resource in InMemoryConfiguration.GetIdentityResources()) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (!context.ApiResources.Any()) { foreach (var resource in InMemoryConfiguration.ApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); } //aspNet Identity serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.Migrate(); var appContext = serviceScope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); appContext.Database.Migrate(); if (!appContext.Roles.Any()) { foreach (var item in ApplicationDbContextDataSeed.Roles) { appContext.Roles.Add(item); } appContext.SaveChanges(); } if (!appContext.Users.Any()) { foreach (var item in ApplicationDbContextDataSeed.Users) { appContext.Users.Add(item); } appContext.SaveChanges(); } if (!appContext.UserRoles.Any()) { foreach (var item in ApplicationDbContextDataSeed.UserRoles) { appContext.UserRoles.Add(item); } appContext.SaveChanges(); } } }
這時候只須要在 configure方法中調用便可
這時候咱們只須要直接運行項目就額能夠生成了 ids4的相關表,以及identity的幾個表了,以下圖:
基於ids4的默認的登錄方法,咱們修改以下(applicationController中):首先要注入
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger _logger;
修改登陸方法以下:
/// <summary> /// Handle postback from username/password login /// </summary> [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginInputModel model, string button) { // check if we are in the context of an authorization request var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); if (ModelState.IsValid) { var user = await _userManager.FindByNameAsync(model.Username); if (user == null) { await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); ModelState.AddModelError(string.Empty, AccountOptions.InvalidUsername); } // validate username/password against in-memory store var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure: false); if (result.Succeeded) { AuthenticationProperties props = null; if (AccountOptions.AllowRememberLogin && model.RememberLogin) { props = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; }; if (context != null) { if (await _clientStore.IsPkceClientAsync(context.ClientId)) { return View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl }); } return Redirect(model.ReturnUrl); } // request for a local page if (Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } else if (string.IsNullOrEmpty(model.ReturnUrl)) { return Redirect("~/"); } else { // user might have clicked on a malicious link - should be logged throw new Exception("invalid return URL"); } } if (result.RequiresTwoFactor) { //return RedirectToAction(nameof(LoginWith2fa), new { model.ReturnUrl, model.RememberLogin }); return RedirectToAction(nameof(SendCode), new { model.ReturnUrl, model.RememberLogin }); } if (result.IsLockedOut) { return RedirectToAction(nameof(Lockout)); } else { //ModelState.AddModelError(string.Empty, "Invalid login attempt."); await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); //return View(model); } } // something went wrong, show form with error var vm = await BuildLoginViewModelAsync(model); return View(vm); }
其中涉及到一個 RequiresTwoFactor 的 二次認證的方法,SendCode,也就是咱們鋪墊了這麼久要說的對象了,方法以下:
/// <summary> /// 發送驗證碼頁面 /// </summary> /// <param name="returnUrl"></param> /// <returns></returns> [HttpGet] [AllowAnonymous] public async Task<ActionResult> SendCode(string returnUrl, bool rememberMe) { var userId = await _signInManager.GetTwoFactorAuthenticationUserAsync(); if (userId == null) { return View("Error"); } //假設默認Email 獲取方式進行驗證//生成二次驗證的 token var twoFactoryToken = await _userManager.GenerateTwoFactorTokenAsync(userId, "Email"); //發送email EmailHelper.SendMail(new EmailInfo() { From = new System.Collections.Generic.List<EmailAddress>() { new EmailAddress("esoftor's framework(esoftor-from)", "1365101128@qq.com") }, To = new System.Collections.Generic.List<EmailAddress>() { new EmailAddress("esoftor's framework(esoftor-to)", "1365101128@qq.com") }, Subject = "esoftor's core 2.x framework 登陸驗證碼", HtmlBody = $"<div style='font-size:18;font-weight:bold'>您正在進行esoftor's core 2.x framework 的二次認證受權登陸,您的驗證碼爲:{twoFactoryToken}</div>" }); //二次驗證方式 email,phone?... var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(userId); var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); SelectList selectLists = new SelectList(factorOptions); return View(new SendCodeViewModel { Providers = selectLists, ReturnUrl = returnUrl }); }
對應的 cshtml頁面以下:
@model ESoftor.Authorization.Server.Models.SendCodeViewModel @{ ViewData["Title"] = "SendCode"; } <h1>SendCode</h1> <h4>SendCodeViewModel</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="SendCode"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <div class="form-group"> <label asp-for="ReturnUrl" class="control-label"></label> <input asp-for="ReturnUrl" class="form-control" /> <span asp-validation-for="ReturnUrl" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="TwoFactoryToken" class="control-label"></label> <input asp-for="TwoFactoryToken" class="form-control" /> <span asp-validation-for="TwoFactoryToken" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Providers" class="control-label"></label> @*@Html.DropDownList("Providers", Model.Providers, new { @class = "form-control custom-select" })*@ <select class="form-control custom-select"> @foreach (SelectListItem item in Model.Providers.Items) { <option value="@item.Value">@item.Text</option> } </select> </div> <div class="form-group"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div>
當咱們點擊這裏的 create的按鈕的時候,就會提交到後臺的驗證碼驗證方法,以下:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> SendCode(SendCodeViewModel model) { if (!ModelState.IsValid) { return View(); } var userId = await _signInManager.GetTwoFactorAuthenticationUserAsync(); //// Generate the token and send it //if (!await _userManager.SendTwoFactorCodeAsync(model.SelectedProvider)) //{ // return View("Error"); //} var twoFactorProviders = await _userManager.GetValidTwoFactorProvidersAsync(userId); //return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl }); //生成二次驗證的 token //var twoFactoryToken = _userManager.GenerateTwoFactorTokenAsync(userId, model.Providers.Where(x => x.Selected).First().Value); //驗證Email的token(code) var validTwoToken = await _userManager.VerifyTwoFactorTokenAsync(userId, "Email", model.TwoFactoryToken); if (validTwoToken) { var twoSignInResult = await _signInManager.TwoFactorSignInAsync("Email", model.TwoFactoryToken, isPersistent: true, rememberClient: false); if (twoSignInResult.Succeeded) return Redirect(model.ReturnUrl); } ModelState.AddModelError(string.Empty, "Invalid Two Factory code."); return View(); }
到這裏就完成了,代碼中哦都有註釋,若干是不清楚,能夠留言。這裏稍微須要注意的就是 core的identity也就是上面注入的 UserManager和SignInManager的兩個方法,和之前的owin不一樣,因此你搜到到的不少資料是驢脣不對馬嘴的,也就是上面加紅的部分,core的identity api中變成了以上的命名方法,若是搜到不一致,不要驚訝,由於咱們這裏是core。
參考圖以下:
項目跑起來以後,登陸中以前,
登陸以後獲取core二次認證,此時能夠收到登陸的短信或者郵件通知,內容包含了登錄所需的驗證碼,同時頁面變爲輸入驗證碼的頁面(右圖)
認證成功登陸中以後,提示獲取受權信息:
完。