.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"); }
                );


            //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>

页面长相如下:

.net5 core Razor项目实战系列之七:用户登录

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");
            }
            bool isRemember = rememberMe == "1" ? true : false;
//还是使用 Lambda 表达式来找匹配的用户 TUser user
= _context.TUsers.Single<TUser>(user => user.SigninAcc == userAcc && user.SignPwd == userPwd); //验证不通过跳转到登录页继续登录 if (user==null || user.UserId<1) { Response.Redirect("/Auth/Signin"); } //验证通过后用以下四步完成登录。 //第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步,指定身份认证方式 这里使用 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的值传递过去,如下图:

.net5 core Razor项目实战系列之七:用户登录

登录成功后直接进入【部门管理】页面,如下:

.net5 core Razor项目实战系列之七:用户登录

如果是直接访问登录页:

.net5 core Razor项目实战系列之七:用户登录

登录成功后则进入首页,如下:

.net5 core Razor项目实战系列之七:用户登录

最后,我们对系统做以下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. 修改部门保存到日志表的时候取 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) //如果用户已经登录
            {
                //取出登录时设置的所有用户信息
                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;
//其他以此类推,登录时设置的值,这里就可以用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; }

 

.net5 core Razor项目实战系列之七:用户登录

上一篇:JS把DOM节点转换为Fragment提高执行效率的原因


下一篇:webpack4 配置教程