2019年12月28日 星期六

Blazor 使用者身分驗證 之 登入與登出

ASP.NET Core Blazor 使用者身分驗證 之 登入與登出

更多關於 Blazor 教學影片,可以參考 Blazor 教學影片播放清單 或者 Blazor 快速體驗教學影片撥放清單。也歡迎訂閱本 .NET / Blazor / Xamarin.Forms 影片頻道 。


在這篇文章中,將會說明如何不使用 ASP.NET Core 上的身分識別 功能 (ASP.NET Core 身分識別是支援使用者介面(UI)登入功能的 API。管理使用者、密碼、設定檔資料、角色、宣告、權杖、電子郵件確認等等。),來實作出登入/登出的效果,並且,也將會說明如何設計使用者有登入與沒有登入的時候,將會看到不同的網頁內容。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載

使用預設專案範本建立 Blazor 專案

想要進行這樣的專案開發練習,可以參考底下的操作步驟
  • 打開 Visual Studio 2019 開發工具
  • 當 [Visual Studio 2019] 對話窗出現之後,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗內,請找出 [Blazor 應用程式] 這個專案開發範本,並且點選這個專案開發範本
  • 請點選右下角 [下一步] 按鈕
  • 出現 [設定新的專案] 對話窗,輸入適當的 [專案名稱] 、 [位置] ,完成後,請點選右下角 [建立] 按鈕

    🕬 說明

    在這個範例程式碼中,將會建立一個 BlazorAuthentication 專案名稱
    #
  • 此時將會看到 [建立新的 Blazor 應用程式] 對話窗,這裡可以根據當時開發專案的需要,自行決定是否有調整 Blazor 專案的其他特性,若無,請點選右下角的 [建立] 按鈕
  • 此時,這個 Blazor 專案已經建立完成

🔑 注意事項

在建立練習專案的時候,無須在 [建立新的 Blazor 應用程式] 對話窗下,點選該對話窗右上方的 [驗證] 設定選項,也就是,維持 [驗證] 選項為 [無驗證]
#

加入本專案會用到的 NuGet 套件

  • 滑鼠右擊這個專案節點
  • 選擇 [管理 NuGet 套件] 選項
  • 點選 [瀏覽] 標籤頁次
  • 請記得在這裡一定要勾選 [包括搶鮮版] 選項
  • 從清單中找到 [Microsoft.AspNetCore.Blazor.HttpClient] 項目,請點選這個項目
  • 點選右上方的 [安裝] 按鈕
  • 完成這個 [Microsoft.AspNetCore.Blazor.HttpClient] NuGet 套件的安裝

建立相關方案資料夾

  • 滑鼠右擊這個專案節點
  • 選擇 [加入] > [新增資料夾] 選項
  • 使用 Controllers 名稱作為該方案資料夾的名稱

建立登入與登出的 Razor Page 頁面

建立登入 Razor Page 頁面

  • 滑鼠右擊這個專案節點內的 [Pages] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 頁面] 選項
  • 在下方 [名稱] 欄位內,輸入 Login.cshtml
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 打開 [Pages] 資料夾內的 [Login.cshtml] 檔案
  • 使用底下 Razor 標記語法替換掉這個檔案內的內容
@page
@model BlazorAuthentication.Pages.LoginModel
@{
    ViewData["Title"] = "登入";
}
<h2>登入</h2>
  • 在 [Pages] 資料夾內,找到 [Login.cshtml] 節點內的 [Login.cshtml.cs]
  • 使用滑鼠雙擊這個 [Login.cshtml.cs] 節點,打開這個檔案內容
  • 使用底下 C# 程式碼替換掉原先這個檔案內的內容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

#region 這裡將會是新加入的命名空間宣告
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
#endregion

namespace BlazorAuthentication.Pages
{
    [AllowAnonymous]
    public class LoginModel : PageModel
    {
        public string ReturnUrl { get; set; }
        public async Task<IActionResult> OnGetAsync(string paramUsername, string paramPassword)
        {
            string returnUrl = Url.Content("~/");
            try
            {
                // 清除已經存在的登入 Cookie 內容
                await HttpContext
                    .SignOutAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme);
            }
            catch { }

            #region 這裡將會要針對傳入的使用者帳號與密碼進行驗證

            #region 本練習簡化將不做任何驗證,不過,本練習將簡化不做任何設計

            #endregion

            #region 加入這個使用者需要用到的 宣告類型 Claim Type
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, paramUsername),
                new Claim(ClaimTypes.Role, "Administrator"),
            };
            #endregion

            #region 建立 宣告式身分識別
            // ClaimsIdentity類別是宣告式身分識別的具體執行, 也就是宣告集合所描述的身分識別
            var claimsIdentity = new ClaimsIdentity(
                claims, CookieAuthenticationDefaults.AuthenticationScheme);
            #endregion

            #region 建立關於認證階段需要儲存的狀態
            var authProperties = new AuthenticationProperties
            {
                IsPersistent = true,
                RedirectUri = this.Request.Host.Value
            };
            #endregion

            #region 進行使用登入
            try
            {
                await HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity),
                authProperties);
            }
            catch (Exception ex)
            {
                string error = ex.Message;
            }
            #endregion

            #endregion

            return LocalRedirect(returnUrl);
        }
    }
}

建立登出 Razor Page 頁面

  • 滑鼠右擊這個專案節點內的 [Pages] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 頁面] 選項
  • 在下方 [名稱] 欄位內,輸入 Logout.cshtml
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 打開 [Pages] 資料夾內的 [Logout.cshtml] 檔案
  • 使用底下 Razor 標記語法替換掉這個檔案內的內容
@page
@model BlazorAuthentication.Pages.LogoutModel
@{
    ViewData["Title"] = "登出";
}
<h2>登出</h2>
  • 在 [Pages] 資料夾內,找到 [Logout.cshtml] 節點內的 [Logout.cshtml.cs]
  • 使用滑鼠雙擊這個 [Logout.cshtml.cs] 節點,打開這個檔案內容
  • 使用底下 C# 程式碼替換掉原先這個檔案內的內容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

#region 這裡將會是新加入的命名空間宣告
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
#endregion

namespace BlazorAuthentication.Pages
{
    public class LogoutModel : PageModel
    {
        public async Task<IActionResult> OnGetAsync()
        {
            // 清除已經存在的登入 Cookie 內容
            await HttpContext
                .SignOutAsync(
                CookieAuthenticationDefaults.AuthenticationScheme);
            return LocalRedirect(Url.Content("~/"));
        }
    }
}

建立登出入使用的 Blazor Razor Component 元件

  • 滑鼠右擊這個專案節點內的 [Shared] 資料夾
  • 選擇 [加入] > [新增項目] 選項
  • 在 [新增項目] 對話窗內,選擇 [Razor 頁面] 選項
  • 在下方 [名稱] 欄位內,輸入 LoginControl.razor
  • 點選 [新增] 按鈕,完成這個登入 Razor 頁面的建立
  • 打開 [Shared] 資料夾內的 [LoginControl.razor] 檔案
  • 使用底下 Razor 標記語法替換掉這個檔案內的內容
@page "/LoginControl"
<AuthorizeView>
    <Authorized>
        <b>Hello, @context.User.Identity.Name!</b>
        <a class="ml-md-auto btn btn-primary"
           href="/logout?returnUrl=/"
           target="_top"> 登出 </a>
    </Authorized>
    <NotAuthorized>
        <input type="text"
               placeholder="User Name"
               @bind="@Username" />
        &nbsp;&nbsp;
        <input type="password"
               placeholder="Password"
               @bind="@Password" />
        <a class="ml-md-auto btn btn-primary"
           href="/login?paramUsername=@Username&paramPassword=@Password"
           target="_top"> 登入 </a>
    </NotAuthorized>
</AuthorizeView>
@code {
    string Username = "";
    string Password = "";
}

進行 ASP.NET Core 專案的啟動設定修正

  • 在這個專案內找到 [Startup.cs] 檔案
  • 使用滑鼠雙擊這個檔案,打開這個檔案
  • 使用底下 C# 程式碼替換掉原有的檔案內容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using BlazorAuthentication.Data;

#region 這裡將會是新加入的命名空間宣告
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System.Net.Http;
#endregion

namespace BlazorAuthentication
{
    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.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            #region 加入使用 Cookie 認證需要的宣告
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = Microsoft.AspNetCore.Http.SameSiteMode.None;
            });
            services.AddAuthentication(
                CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie();
            #endregion

            services.AddRazorPages();
            services.AddServerSideBlazor();
            services.AddSingleton<WeatherForecastService>();

            #region 加入會用到的服務宣告
            services.AddHttpContextAccessor();
            services.AddScoped<HttpContextAccessor>();
            services.AddHttpClient();
            services.AddScoped<HttpClient>();
            #endregion
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            #region 指定要使用 Cookie & 使用者認證的中介軟體
            app.UseCookiePolicy();
            app.UseAuthentication();
            #endregion

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");

                #region 這裡要加入 Web API 控制器會用到的路由
                endpoints.MapControllerRoute("default", "{controller=Home}/action=Index/{id}");
                #endregion
            });
        }
    }
}

修正這個 Blazor 專案的整體路由設定

  • 在該專案根目錄下,找到並且打開 [App.razor] 檔案
  • 使用底下 Razor 元件 標記語法替換掉這個檔案內的內容
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>

修正主要版片配置的內容

  • 在該專案的 [Shared] 目錄下,找到並且打開 [MainLayout.razor] 檔案
  • 使用底下 Razor 元件 標記語法替換掉這個檔案內的內容
@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        @*在這裡加入剛剛建立的登出入 Blazor 元件*@
        <LoginControl />
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

修正該專案的首頁 Blazor 元件

  • 在該專案的 [Pages] 目錄下,找到並且打開 [Index.razor] 檔案
  • 使用底下 Razor 元件 標記語法替換掉這個檔案內的內容
@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<AuthorizeView>
    <Authorized>
        <h1>你好, @context.User.Identity.Name!</h1>
        <p>因為已經通過身分驗證成功,所以,會看到這個訊息</p>
    </Authorized>
    <NotAuthorized>
        <p>你尚未通過身分驗證,所以,會看到這個訊息</p>
    </NotAuthorized>
</AuthorizeView>

執行專案

  • 請執行這個 Blazor 專案
  • 將會看到如下圖的網頁顯示出來
  • 在網頁的最上方,將會看到剛剛設計的 [LoginControl.razor] 元件
  • 因為現在尚未通過身分驗證,所以,在 [LoginControl.razor] 元件上將會看到登入按鈕
  • 而且,在首頁上因為使用了 AuthorizeView 這個 Blazor 內建的元件,因此,將會根據使用者是否有成功通過驗證,而顯示出不同的內容
  • 由於現在尚未通過驗證,因此,在首頁上將會顯示尚未通過驗證的訊息
  • 請在 [User Name] 與 [Password] 欄位,輸入使用的帳號與密碼 (因為在這個練習專案中,並未實際做使用帳號與密碼的檢查,因此,可以輸入任何內容即可)
  • 請點選右上方的 [登入] 按鈕,如下面螢幕截圖
  • 在成功通過身分驗證過程,也就是登入成功了,在網頁最上方的 [LoginControl.razor] 元件內容也自動切換成已經登入的狀態
  • 並且,在首頁頁面上,也因為成功登入了,顯示出已經登入訊息,這裡的內容將會動態的切換,與尚未成功登入前有所不同
  • 因為採用 Cookie 的方式,所以,不論關閉這個網頁,或者開啟一個新的瀏覽器標籤頁次,都會看到如上面螢幕截圖的畫面



沒有留言:

張貼留言