.net5 core Razor項目實戰系列之七:用戶登錄


引言:

前幾篇初步演示了基於.net core Razor 的 Web 框架進行開發的規則和風格,

以及如何使官方的ORM框架EntityFrameworkCore做數據庫的交互,

.net5.0 core(底層平台) + Razor(終端交互) + EF Core(數據庫訪問) + MySql(數據庫服務)這樣

一個組合運作起來還是很簡潔高效的。基於.net core的跨平台底層 + Razor(PC端) + WebApi(數據服務)的組合

算是回歸到 Web 的本源了,能適配大部分企業級的應用場景很好、很強大,筆者很喜歡。

 

本篇介紹如何實現登錄功能。

具體來說就是用戶訪問Auth下的頁面的時候需要先登錄后才能訪問,其他頁面不受限制。

實現這樣一個業務場景只需要幾個簡單配置和少許編碼就可以了,具體步驟如下:

1. Startup.cs中配置如下:

ConfigureServices(IServiceCollection services)方法加入如下代碼(見紅色部分):

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages(options => {
                //設置訪問Auth文件夾下的頁面都需要經過驗證。
                options.Conventions.AuthorizeFolder("/Auth");
                //因為 Signin.cshtml 是登錄頁,所以可以匿名訪問。
                options.Conventions.AllowAnonymousToPage("/Auth/Signin");
         
                //上面兩行設置可以用下面的這行替換(鏈式調用)
                //options.Conventions.AuthorizeFolder("/Auth").AllowAnonymousToPage("/Auth/Signin");
            });

            //身份驗證,設置身份驗證方式為 Cookie 驗證(網頁應用程序當然只適合用 cookie)
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(
                options => { options.LoginPath = new PathString("/Auth/Signin"); } //設置登錄頁面為/Auth/Signin
                );


            //for SQL Server
            //services.AddDbContext<AuthDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MySql")));

            //for MySql
            services.AddDbContext<AuthDbContext>(options => options.UseMySql(
                Configuration.GetConnectionString("MySql"),ServerVersion.AutoDetect(Configuration.GetConnectionString("MySql"))));
 
        }

 Configure(IApplicationBuilder app, IWebHostEnvironment env) 方法加入如下代碼(見紅色部分):

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();

            app.UseRouting();

            //啟用身份驗證,加上這句才能生效,
            //注意順序,在 app.UseRouting(); 和 app.UseAuthorization();之間
 app.UseAuthentication(); 

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            { 
                endpoints.MapRazorPages();
            });
        }

2. 在 Auth 文件夾下新增登錄頁面 Signin.cshtml ,代碼如下:

Signin.cshtml中代碼:

@page
@model AuthManagement.Pages.Auth.SigninModel
@{
    ViewData["Title"] = "用戶登錄";
}
 
<form method="post">
    <table style="margin-left:160px; background-color:#f7f7f7;border:solid 1px #c0c0c0;"> 
        <tr> 
            <td style="padding:10px 30px 0 30px;">賬號:<br />
            <input type="text" name="acc" /></td>
        </tr>
        <tr>
            <td style="padding:10px 30px 0 30px;">密碼:<br />
            <input type="password" name="pwd" /></td>
        </tr>
        <tr>
            <td style="padding: 10px 30px 10px 30px;">
                <input type="checkbox" name="rememberMe" value="1" /> 記住我 &nbsp;&nbsp;&nbsp;
                <button type="submit">登錄</button>
            </td> 
        </tr> 
    </table>
</form>

頁面長相如下:

Signin.cshtml.cs 文件代碼如下:

namespace AuthManagement.Pages.Auth
{
    public class SigninModel : PageModel
    {

        private readonly AuthDbContext _context;

        //構造函數中對AuthDbContext做依賴注入
        public SigninModel(AuthDbContext context)
        {
            _context = context;
        }

        public void OnGet()
        {
        } 

        // Signin.cshtml 文件中的form不需要指定action,以 Post 方式提交數據我們只要按約定實現 OnPost() 方法就可以了。
        public async Task OnPost()
        {
            string userAcc = Request.Form["acc"];//接收頁面傳遞過來的賬號
            string userPwd = Request.Form["pwd"];//接收頁面傳遞過來的密碼
            string rememberMe = Request.Form["rememberMe"];//如果勾選=1,否則=null

            // 對賬號密碼做一下基本的非空判斷可以減少數據庫操作。
            if (string.IsNullOrWhiteSpace(userAcc) || string.IsNullOrWhiteSpace(userPwd))
            {
                Response.Redirect("/Auth/Signin");
                return; //驗證不通過就不繼續向下執行了
            }
            bool isRemember = rememberMe == "1" ? true : false;

            //還是使用 Lambda 表達式來找匹配的用戶
            TUser user = _context.TUsers.FirstOrDefault<TUser>(user => user.SigninAcc == userAcc && user.SignPwd == userPwd && user.IsValid==1);
            
            //驗證不通過跳轉到登錄頁繼續登錄
            if (user==null || user.UserId<1) 
            {
                Response.Redirect("/Auth/Signin");
                return;//驗證不通過就不繼續向下執行了
            }

            //驗證通過后用以下四步完成登錄。
            //第1步,設置 Cookie中要包含的用戶信息
            List<Claim> claims = new List<Claim>
            {
                //特別注意這一行,如果不設置那么 HttpContext.User.Identity.Name 將為 null 
                new Claim(ClaimTypes.Name, user.SigninAcc), 
                //下面這幾行隨意,根據需要添加
                new Claim("UserId", user.UserId.ToString()),
                new Claim("UserName", user.UserName),
                new Claim("DeptId", user.DeptId.ToString()),
                new Claim("DeptName", user.DeptName)
            };

            //第2步,用第1步中的信息生成身份標識(身份認證方式是 cookie 認證)
            ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

            //第3步,設置 cookie 屬性,比如過期時間,是否持久化等
            string returnUrl = Request.Query["ReturnUrl"];
            AuthenticationProperties authProperties = new AuthenticationProperties
            {
                IsPersistent = isRemember,//是否持久化
                //如果用戶點“登錄“進來,登錄成功后跳轉到首頁,否則跳轉到上一個頁面
                RedirectUri = string.IsNullOrWhiteSpace(returnUrl) ? "/Index" : returnUrl,
                ExpiresUtc = DateTime.UtcNow.AddMonths(1) //設置 cookie 過期時間:一個月后過期
            };

            //第4步,調用 HttpContext.SignInAsync() 方法生成一個加密的 cookie 並輸出到瀏覽器。
            await HttpContext.SignInAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties
            );
        }
    }
}

至此,一個標准的 cookie 登錄流程就完成了,當用戶瀏覽網站的時候,如果點 【部門管理】將啟用身份驗證,

頁面會自動跳轉到登錄頁面,同時將【部門管理】的網址作為查詢參數ReturnUrl的值傳遞過去,如下圖:

登錄成功后系統會自動獲取ReturnUrl參數的值(這里是 /Auth/DeptList)后進入【部門管理】頁面,如下:

如果是直接訪問登錄頁:

登錄成功后則進入首頁,如下:

最后,我們對系統之前的功能做以下2點完善:

1. 在頁面右上角加上登錄(未登錄)和登出(已登錄)的按鈕。

2. 修改部門保存到日志表的時候 UserId 和 UserName 我們取cookie中記錄的用戶編號和用戶名稱而不是寫死。

代碼如下:

1. 打開_Layout.cshtml 文件加上 登(未登錄)和登出(已登錄)按鈕,見紅色部分代碼:

<ul class="navbar-nav flex-grow-1">
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
    </li>

    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-page="/Auth/DeptList">【部門管理】</a>
    </li>

    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
    </li>
                         
    <li class="nav-item">
        @if (Context.User.Identity.IsAuthenticated) //已登錄顯示[登出]按鈕
        {
            <a class="nav-link text-dark" asp-area="" asp-page="/Auth/Signout">[登出]</a>
        }
        else
        {
            <a class="nav-link text-dark" asp-area="" asp-page="/Auth/Signin">[登錄]</a>
        }
    </li> 
</ul>

在 Auth 文件夾下新增 Signout.cshtml 頁面,Signout.cshtml.cs 中代碼如下:

namespace AuthManagement.Pages.Auth
{
    public class SignoutModel : PageModel
    {
        public async Task OnGet()
        {
// 直接使用系統函數做登出,只需這一行代碼就可以了
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); // 登出后跳轉到首頁 Response.Redirect("/Index"); } } }

 2. 打開 DeptEdit.cshtml.cs 頁面 , 修改保存到日志表的這部分代碼(見紅色部分),

UserId 和 UserName 的取值由固定值改成取 cookie 中的 UserId 和 UserName 的值 ,代碼:

        /// <summary>
        /// 將更改前和更改后的數據保存到t_log表
        /// </summary>
        /// <returns></returns>
        private List<TLog> GenerateLog()
        {
            int userId = 0;
            string userName = "未知";

            //取出登錄時設置的用戶信息
            ClaimsPrincipal cp = HttpContext.User;
            if (cp.Identity.IsAuthenticated) //如果用戶已經登錄
            {
          //取出用戶帳號信息
                //***注:如果我們登錄時寫入 Cookie 中的是 UserId 如: new Claim(ClaimTypes.Name, user.UserId),
                //***那么這里取出來的將是 UserId , 即 Identity.Name 對應的是 ClaimTypes.Name 的值。
                string userAcc = cp.Identity.Name;
//取出登錄時設置到 cookie 中的所有用戶信息 List<Claim> claims = cp.Claims.ToList<Claim>(); //通過傳入 Lambda 表達式找出登錄時設置的 UserId 值 string uid = claims.Single<Claim>(option => option.Type == "UserId").Value; userId = Convert.ToInt32(uid); //通過傳入 Lambda 表達式找出登錄時設置的 UserName 值 userName = claims.Single<Claim>(option => option.Type == "UserName").Value;
//其他以此類推,登錄時設置到 cookie 中的值都可以用Lambda表達式取出來 string deptId = claims.Single<Claim>(option => option.Type == "DeptId").Value; string deptName = claims.Single<Claim>(option => option.Type == "DeptName").Value; } string batchNo = Guid.NewGuid().ToString(); TLog beforeLog = new TLog { UserId = userId, UserName = userName, BatchNo = batchNo, TableName = "t_dept", TableData = "", LogTime = DateTime.Now }; TLog afterLog = new TLog { UserId = userId, UserName = userName, BatchNo = batchNo, TableName = "t_dept", TableData = "", LogTime = DateTime.Now }; List<TLog> logList = new List<TLog>(); logList.Add(beforeLog); logList.Add(afterLog); return logList; }

注:上面 userAcc 、 deptId 、 deptName這三個值雖然取出來了但是沒有用到,

這里只是為了演示如何通過 Cookie 設置值和取值,

實際項目中如果不需要 , 那么在登錄的時候就不用寫到 Cookie 中去了。

 


免責聲明!

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



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