ASP.NET Core 3.1 實際操作摸索學習 (Identity部分)- 1


手頭一個小應用項目,非常非常小。。。

主要功能也就是提供一個網頁頁面瀏覽數據,數據內容部分在數據庫中,圖像部分在阿里OSS對象存儲中;

最最前期采用ASP.NET WebForm做過一些WEB應用開發,再后來學習過ASP.NET MVC4,一直也沒有機會實戰;

通過近期的學習考慮,准備本次小應用直接通過ASP.NET Core最新版 3.1 來架構,雖然有點大馬拉小車,但也是個難得的實踐機會;

碰到的第一個問題就是,小應用雖小,但也是一個內部應用項目,並不是對外部用戶全開放的,需要為用戶配置登錄賬號,用戶通過賬號登錄后才可以進行頁面瀏覽查詢;

以前了解過MemberShip 和MVC4 框架下的Identity ,總體來說,當時總覺得好像邏輯好復雜,自定義部分很不順(當然也可能是沒深入研究的原因);

而完全自己開發一個網頁應用的論證登錄框架好像有點太過了,目前還沒那能力,個人還是以開發實際功能應用為主的,並不是框架開發高手;

所以,本次應用准備采用ASP.NET Core 3.1自帶的Identity 做用戶管理部分框架,深入學習Identity ,並考慮一些簡單擴展(例如:管理員對用戶的管理、批量導入新建等等)

准備環境: VS2019 版本號:16.6.2 , ASP.NET Core 版本 3.1

學習一:研究默認解決方案自帶個人認證

 第一步:新建一個帶個人認證的ASP.NET Core Web應用程序

 項目命名為 MyCoreTest1

 

 選擇 Web應用程序(MVC) (該架構也可以加入Restful WEB API的內容,比較符合我的應用要求)

然后在右側身份驗證點擊更改 -> 選擇 個人用戶賬戶 -> 選擇默認的存儲應用內的用戶賬戶 最后點擊 確認按鈕:

注:選擇了身份驗證后,會自動只能選擇HTTPS,后面會有幾個窗口需要點確定(好像是和SSL證書相關的)

最后點創建按鈕,就自動把一個項目建好了:

 

 

 

直接跑起來看看吧,按F5 或者 Ctrl+F5:

 

 

 標題欄右側 有兩個按鈕,分別為 Register 和 Login,既然是全新的應用,目前肯定沒用戶,那就點Register按鈕試試看:

 

 默認Identity框架用email 作為用戶賬號,密碼有一定的安全策略(必須超過6位、必須帶大寫字符、小寫字符、數字、特殊字符)

 

 

 

 

 輸入賬號: test@test.com ,密碼 P@ssw0rd ,然后點 Register按鈕,出現一個數據庫錯誤;

當然是因為還沒有建數據庫的原因,直接點擊中間的 Apply Migrations 來讓應用自動創建數據庫;

 

 按鈕旁邊出現: Try refreshing the page ,則表示數據庫創建好了,(后面會提到數據庫創建到哪里去了。),

 

 按F5刷新頁面,會進入 注冊確認 頁面,默認框架對於用於注冊必須有一個確認動作,主要是預留給有 郵件確認需求的;(即用來確認用戶是否真的擁有這個郵箱)

如果不在這個頁面點擊 Click here to confirm your account,會發生能登記賬號,但不能登錄的情況:

 

 

 點擊后,出現 Confirm email 信息,表示用戶已確認:

 

 這個時候點擊右上角  Login :  輸入剛才登記的用戶信息: (test@test.com , 密碼:P@ssw0rd)

 

登錄后,右上角出現 注冊用戶email ,則表示登錄成功:

 

 哇,真的很神奇,一句代碼沒寫,一個基本用戶注冊、登錄功能全實現了!

現在,查看一下數據庫被建在哪里了,以及為啥會被建到那里,打開 視圖 --> SQL Server 對象資源管理器

 

 

原來數據庫被建在Localdb 里了:

 

打開后,可以看到已經建立如下表:

 

 打開AspNetUsers 表可以看到里面剛才新建的一個用戶:

 

 去查看一下項目中的 appsettings.json 配置文件,其中定義了數據庫連接字符串 (DefaultConnection):

{
  "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-MyCoreTest1-3DF9EC28-CCC2-495F-AF13-FE6E503EC9F6;Trusted_Connection=True;MultipleActiveResultSets=true" },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

同時在Startup.cs中有一段代碼定義使用這個字符串:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddControllersWithViews();
            services.AddRazorPages();
        }

 可根據需要修改字符串名 及 數據庫的字符串值 來連接自己希望的數據庫;

先再建個新的空數據庫,起名:MyCoreTestDB1

 

 

 

 在appsettings.json 配置文件增加一個名為 MyConnection 連接字符串:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-MyCoreTest1-3DF9EC28-CCC2-495F-AF13-FE6E503EC9F6;Trusted_Connection=True;MultipleActiveResultSets=true",
    "MyConnection": "Server=(localdb)\\mssqllocaldb;Database=MyCoreTestDB1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("MyConnection")));
            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddControllersWithViews();
            services.AddRazorPages();
        }

然后再跑起來看看,點擊 Login

 

 

就出現了數據庫表不存在的錯誤,繼續點擊 Apply Migrations 后再刷新,顯示 Invalid Login attempt (當然啦,數據庫是新的啦,沒有用戶)

 

 

 按上面注冊用戶步驟,完成用戶注冊及用戶確認,然后再次登錄:

默認建好的帶個人認證的解決方案,目錄結構如下:

 

發現一個問題,那些 注冊用戶、登錄頁面 代碼在哪里呢? (登錄界面還能看到有個View:  _LoginPartial.cshtml ,注冊用戶頁面直接啥也沒有) 

在 Identity 的 Areas 里只有一個Page: _ViewStart.cshtml。。。

我只能猜測應該是封裝后,編譯到DLL里了。

那么問題來了,如果我覺得這些默認頁面不是我想要的,或者說上面一些信息我需要修改一下,怎么辦?

右鍵點擊 項目: MyCoreTest1 然后點擊  添加 --> 新搭建基架的項目

 

 左側選擇 標識 ,然后中間選擇 標識 (這NB的翻譯。。。)

會自動搭建基架 ,安裝一些NuGet包:

 

 彈出了,可以重載的內容: (翻譯成替代,我也真服了) 

 這次我就先重載 注冊頁 和 登錄頁,然后數據上下文類也先用原本的: (會發現沒辦法點用戶類旁邊的加號,也即不能用自己定義的用戶類,后面再說怎么用自己定義的用戶類)

點 添加后,VS自動開始加載代碼:

 

 

 

 首先會彈出一個ReadMe: 這個鏈接可以去看看: https://go.microsoft.com/fwlink/?linkid=2116645

 

資源管理器里可以看到Areas -> Identity -> Pages 下加入了Account 目錄, 有了Login.cshtml 和 Register.cshtml

 雙擊 Login.cshtml會發現 這個不是一個原來理解的View ,而是 ASP.NET Core 新的一種類似於原來aspx的開發方式: Razor Pages

在此代碼基礎上就可以進行修改來實現自己的一些要求或設想;

首先,我目前還不考慮第三方外部認證方式,直接就把頁面上相關顯示的DIV刪除:

@page
@model LoginModel

@{
    ViewData["Title"] = "Log in";
}

<h1>@ViewData["Title"]</h1>
<div class="row">
    <div class="col-md-4">
        <section>
            <form id="account" method="post">
                <h4>Use a local account to log in.</h4>
                <hr />
                <div asp-validation-summary="All" class="text-danger"></div>
                <div class="form-group">
                    <label asp-for="Input.Email"></label>
                    <input asp-for="Input.Email" class="form-control" />
                    <span asp-validation-for="Input.Email" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Input.Password"></label>
                    <input asp-for="Input.Password" class="form-control" />
                    <span asp-validation-for="Input.Password" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <div class="checkbox">
                        <label asp-for="Input.RememberMe">
                            <input asp-for="Input.RememberMe" />
                            @Html.DisplayNameFor(m => m.Input.RememberMe)
                        </label>
                    </div>
                </div>
                <div class="form-group">
                    <button type="submit" class="btn btn-primary">Log in</button>
                </div>
                <div class="form-group">
                    <p>
                        <a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
                    </p>
                    <p>
                        <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
                    </p>
                    <p>
                        <a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
                    </p>
                </div>
            </form>
        </section>
    </div>    
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

好了,頁面清爽一點了:

 

 

 下一步,我想用戶注冊不需要強制用Email注冊

打開 Register.cshtml.cs 在 InputModel中增加一個UserName 字段,必須輸入、長度1-20,顯示名為 賬號:

同時修改  OnPostAsync 方法中一個賦值,將UserName直接賦值給UserName,而不是把email賦值給UserName;

        public class InputModel
        {
            [Required] [StringLength(20, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)] [Display(Name = "賬號")] public string UserName { get; set; }

            [Required]
            [EmailAddress]
            [Display(Name = "Email")]
            public string Email { get; set; }

            [Required]
            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
            [DataType(DataType.Password)]
            [Display(Name = "Password")]
            public string Password { get; set; }

            [DataType(DataType.Password)]
            [Display(Name = "Confirm password")]
            [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
            public string ConfirmPassword { get; set; }
        }
        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            if (ModelState.IsValid)
            {
                var user = new IdentityUser { UserName = Input.UserName, Email = Input.Email };
                var result = await _userManager.CreateAsync(user, Input.Password);
                //.........省略其他下面的代碼
            }

修改 Register.cshtml 增加一項輸入,並且刪除 第三方外部用戶注冊部分的DIV:

@page
@model RegisterModel
@{
    ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>

<div class="row">
    <div class="col-md-4">
        <form asp-route-returnUrl="@Model.ReturnUrl" method="post">
            <h4>Create a new account.</h4>
            <hr />
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group"> <label asp-for="Input.UserName"></label> <input asp-for="Input.UserName" class="form-control" /> <span asp-validation-for="Input.UserName" class="text-danger"></span> </div>
            <div class="form-group">
                <label asp-for="Input.Email"></label>
                <input asp-for="Input.Email" class="form-control" />
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>
            //....省略下面一些代碼
        </form>
    </div>    
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

跑起來后,可以采用賬號 + Email + 密碼 來注冊賬號,(這里沒有把Email的強制輸入關閉,主要是后面還有個確認頁面是傳送email過去的,先留着這個強制了)

 

 但是發現這個用戶雖然注冊成功,但是在登錄頁面無法登錄,email也不行,賬號也不行。。。

通過查看Login頁面的OnPostAsync 方法中驗證用戶的函數調用,才發現是硬代碼把 email輸入當做 username來驗證了,是因為原先的框架 username 等於 email 

 

 知道原因就好修改了,先修改InputModel:

        public class InputModel
        {
            [Required] [Display(Name = "賬號")] public string UserName { get; set; }

            [Required]
            [DataType(DataType.Password)]
            public string Password { get; set; }

            [Display(Name = "Remember me?")]
            public bool RememberMe { get; set; }
        }
var result = await _signInManager.PasswordSignInAsync(Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: false);

再修改一下頁面:

@page
@model LoginModel

@{
    ViewData["Title"] = "Log in";
}

<h1>@ViewData["Title"]</h1>
<div class="row">
    <div class="col-md-4">
        <section>
            <form id="account" method="post">
                <h4>Use a local account to log in.</h4>
                <hr />
                <div asp-validation-summary="All" class="text-danger"></div>
                <div class="form-group">
                    <label asp-for="Input.UserName"></label>
                    <input asp-for="Input.UserName" class="form-control" />
                    <span asp-validation-for="Input.UserName" class="text-danger"></span>
                </div>
                <div class="form-group">
                    <label asp-for="Input.Password"></label>
                    <input asp-for="Input.Password" class="form-control" />
                    <span asp-validation-for="Input.Password" class="text-danger"></span>
                </div>
                //.....省略下面一些代碼
            </form>
        </section>
    </div>    
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

 搞定,用testuser賬號登錄:

下一步,想修改一下取消email確認,(至於完善email確認,在后期再補充考慮),並且在用戶注冊后,直接自動Login 進入;

注釋掉 和郵件確認相關的代碼,並且把新建一個IdentityUser中直接硬編碼 EmailConfirmed = true

再把SignInAsync 第2個參數改為 True,即創建完用戶就直接登錄:

        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl ?? Url.Content("~/");
            //ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
            if (ModelState.IsValid)
            {
                var user = new IdentityUser { UserName = Input.UserName, Email = Input.Email, EmailConfirmed = true };
                var result = await _userManager.CreateAsync(user, Input.Password);
                if (result.Succeeded)
                {
                    //_logger.LogInformation("User created a new account with password.");

                    //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    //code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                    //var callbackUrl = Url.Page(
                    //    "/Account/ConfirmEmail",
                    //    pageHandler: null,
                    //    values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
                    //    protocol: Request.Scheme);

                    //await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    //    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                    //if (_userManager.Options.SignIn.RequireConfirmedAccount)
                    //{
                    //    return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
                    //}
                    //else
                    //{
                    await _signInManager.SignInAsync(user, isPersistent: true);
                    return LocalRedirect(returnUrl);
                    //}
                }
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty, error.Description);
                }
            }

 

新建后,直接就登錄了:

 

在一開始沒有全部重載Identity的文件情況下,如果還需對其他文件進行重載,可以繼續 右鍵點擊 項目: MyCoreTest1 然后點擊  添加 --> 新搭建基架的項目

選擇添加標識,然后會從基架檢索信息:

 

 

 

再次出現選擇 標識 可以重載的文件:

 

 

  這次先把 用戶自己維護信息的 Manage\Index加一下,加完以后,Areas->Identity->Pages->Account 多了一個Manage 目錄: 下面就有Index.cshtml

 默認  /Identity/Account/Manage/ 頁面為: (可以修改手機號碼、email、密碼、雙因素認證、以及個人賬號信息的下載或刪除)

 

其中 雙因素認證,本次先不考慮,則准備先去除:

直接打開 \Areas\Identity\Pages\Account\Manage\_ManageNav.cshtml,然后注釋掉一些代碼:

@inject SignInManager<IdentityUser> SignInManager
@{
    var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
    <li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
    <li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
    <li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li> @*@if (hasExternalLogins) { <li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li> } <li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>*@ <li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
</ul>

修改后:

 

后續准備找份詳細資料,能夠羅列出 Identity 下所有各文件選項對應功能意義,這樣就可以根據自己實際情況來考慮是不是要重載。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM