.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" /> 记住我 <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 中去了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人