在 ASP.NET Core 中实现 Cookie 身份验证

目录

一些背景信息

[创建数据库表和 DbContext](#创建数据库表和 DbContext)

[配置 Cookie 身份验证](#配置 Cookie 身份验证)

[创建 RegisterViewModel 和 LoginViewModel 类](#创建 RegisterViewModel 和 LoginViewModel 类)

创建账户控制器

创建用户帐户

登录应用程序

从应用程序中注销

创建注册和登录视图

[创建 HomeController 和 Index 视图](#创建 HomeController 和 Index 视图)


如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

如果您一直在使用 ASP.NET Core,那么您可能已经了解 ASP.NET Core Identity。ASP.NET Core Identity 是一个功能齐全的网站安全框架,它提供了许多特性,例如外部登录和 JSON Web Tokens (JWT) 支持。然而,有时您需要的是一种简单易用且能让您完全掌控数据存储和帐户管理等各个方面的解决方案。这时,ASP.NET Core 的 Cookie 身份验证就派上用场了。本文将介绍 Cookie 身份验证的概念以及如何配置它来保护您的网站。

一些背景信息

在详细介绍 cookie 身份验证的配置和实现细节之前,我忍不住要强调一下 ASP.NET classic 和 ASP.NET Core 在这方面的相似之处。

ASP.NET 1.x 版本推出时,主要有两种身份验证方式:基于 Windows 的身份验证和表单身份验证。表单身份验证也称为 Cookie 身份验证,因为它基于 Cookie 形式的身份验证票据工作。表单身份验证本身不进行任何用户管理。它只是根据是否存在特定的 Cookie 来检查传入的请求是否已通过身份验证,并据此允许或拒绝最终用户的访问。用户帐户管理是开发人员的责任,需要编写自定义代码来实现。这种简单的方法虽然提供了对底层数据存储和用户管理的完全控制,但另一方面也要求您自行编写所有逻辑。

ASP.NET 2.0 中,表单身份验证新增了成员资格、角色和配置文件提供程序。成员资格框架负责用户和角色的管理。这种方法节省了大量编写必要代码的时间。但它也有自身的局限性,例如数据库结构较为僵化,以及用户管理 API 集较为固定。

后来,微软发布了 ASP.NET Identity------一个能够满足现代网站安全需求(例如外部登录)的新框架。

ASP.NET Core 中,我们有两种类似的选项来实现网站安全:ASP.NET Core Identity 或简单的 Cookie 身份验证。我已经在之前的文章中解释过ASP.NET Core Identity,本文余下部分将讨论 Cookie 身份验证。

现在你已经对 cookie 身份验证有了一些了解,让我们开始吧。

首先创建一个新的 ASP.NET Core Web 应用程序,然后按照以下步骤操作。

创建数据库表和 DbContext

当我们决定在 ASP.NET Core 网站中使用 Cookie 身份验证时,数据存储和数据结构由我们自行负责。例如,我们将在 SQL Server 数据库中创建一个简单的表,但您可以使用任何您选择的数据存储方式(例如,NoSQL 数据库)。

创建具有以下结构的数据库表:

如您所见,我们创建了一个名为 MyAppUsers 的表来存储用户信息。该表结构简单,包含四列:Id、UserName、Password 和 Roles。为了保持简洁,我们存储的所有信息均未加密。此外,角色信息也存储在同一个表中,而不是单独创建一个表。

现在在项目根目录下添加 DataAccess 文件夹,并添加 Entity 和 DbContext 类,如下所示:

Table("MyAppUsers")

public class MyAppUser

{

DatabaseGenerated(DatabaseGeneratedOption.Identity)

public int Id { get; set; }

public string UserName { get; set; }

public string Password { get; set; }

public string Roles { get; set; }

}

MyAppUser 类对应于我们刚刚创建的 MyAppUsers 表,并具有相应的属性。

public class MyAppDbContext:DbContext

{

public MyAppDbContext(DbContextOptions<MyAppDbContext>

options) : base(options)

{

}

public DbSet<MyAppUser> MyAppUsers { get; set; }

}

MyAppDbContext 类继承自 DbContext,并包含 MyAppUsers DbSet。

好的。现在我们已经准备好了 DbContext,接下来让我们为 Web 应用程序启用 Cookie 身份验证。打开 Startup 类并按如下所示修改 ConfigureServices() 方法:

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc();

services.AddEntityFrameworkSqlServer();

services.AddDbContext<MyAppDbContext>(options =>

options.UseSqlServer("data source=.;initial

catalog=northwind;integrated security=true;

multipleactiveresultsets=true"));

services.AddAuthentication

(CookieAuthenticationDefaults.AuthenticationScheme)

.AddCookie();

}

除了以粗体字标记的代码外,ConfigureServices() 方法对您来说应该很熟悉。AddAuthentication() 和 AddCookie() 方法会将 Cookie 身份验证服务注册到框架中。请注意,AddAuthentication() 方法接受一个字符串参数,用于指定安全方案的名称。该值可以是开发人员定义的任何值,也可以使用CookieAuthenticationDefaults 类的 AuthenticationScheme 属性指定的默认值。

此外,请务必根据您的配置更改 AddDbContext() 调用中的数据库连接字符串。您也可以从配置文件中获取该字符串。

接下来,修改 Configure() 方法以使用 cookie 身份验证中间件:

public void Configure(IApplicationBuilder app,

IHostingEnvironment env)

{

app.UseDeveloperExceptionPage();

app.UseAuthentication();

app.UseMvcWithDefaultRoute();

}

我们刚刚完成了网站的 cookie 身份验证配置。

创建 RegisterViewModel 和 LoginViewModel 类

我们需要两个视图模型------RegisterViewModel 和 LoginViewModel------类如下所示:

public class RegisterViewModel

{

Required

public string UserName { get; set; }

Required

public string Password { get; set; }

Required

Compare("Password")

public string ConfirmPassword { get; set; }

}

RegisterViewModel 类封装了在注册视图(稍后讨论)中输入的注册详细信息。

public class LoginViewModel

{

Required

public string UserName { get; set; }

Required

public string Password { get; set; }

Required

public bool RememberMe { get; set; }

}

LoginViewModel 类封装了用户在登录视图中输入的登录信息。请注意 RememberMe 属性,它指示是否应在关闭浏览器会话后保留用户的登录状态。

创建账户控制器

视图模型准备就绪后,在 Controllers 文件夹下添加 AccountController 类。AccountController 将包含五个操作:

  • Register() - Register() 操作的 GET 和 POST 版本负责创建新的用户帐户。
  • Login() - Login() 操作的 GET 和 POST 版本负责将用户登录到系统中。系统会在此过程中向用户颁发身份验证 cookie。
  • Logout() - Logout() 的 POST 版本会删除之前颁发的身份验证 cookie。

让我们逐一分析这些方法。

上述操作需要在构造函数中注入 MyAppDbContext。因此,首先将以下代码添加到 AccountController 中:

private MyAppDbContext db;

public AccountController(MyAppDbContext db)

{

this.db = db;

}

创建用户帐户

以下代码展示了 Register() 操作的两种版本:

public IActionResult Register()

{

return View();

}

HttpPost

public IActionResult Register(RegisterViewModel model)

{

if (ModelState.IsValid)

{

MyAppUser user = new MyAppUser();

user.UserName = model.UserName;

user.Password = model.Password;

user.Roles = "Manager,Administrator";

db.MyAppUsers.Add(user);

db.SaveChanges();

ViewData["message"] = "User created successfully!";

}

return View();

}

POST 版本的 Register() 方法通过模型绑定接收 RegisterViewModel 对象。在该方法内部,我们将值从 RegisterViewModel 传递到 MyAppUser,然后将新用户添加到 MyAppUsers 数据库集合中。请注意,我们还将 Roles 属性设置为 Manager 和 Administrator。在更实际的场景中,通常会有一个单独的页面用于为用户分配角色。调用 SaveChanges() 方法会在我们最初创建的 MyAppUsers 表中创建用户帐户。

我们还设置了一条成功消息到 ViewData 中,该消息可以在注册视图中显示。请注意,为了简化起见,我们没有对创建的用户帐户添加任何验证和检查。

登录应用程序

下面展示了 Login() 函数的两个版本:

public IActionResult Login(string returnUrl)

{

return View();

}

HttpPost

public IActionResult Login(LoginViewModel model,

string returnUrl)

{

bool isUservalid = false;

MyAppUser user = db.MyAppUsers.Where(usr =>

usr.UserName == model.UserName &&

usr.Password == model.Password).SingleOrDefault();

if(user!=null)

{

isUservalid = true;

}

if(ModelState.IsValid && isUservalid)

{

var claims = new List<Claim>();

claims.Add(new Claim(ClaimTypes.Name, user.UserName));

string[] roles = user.Roles.Split(",");

foreach (string role in roles)

{

claims.Add(new Claim(ClaimTypes.Role, role));

}

var identity = new ClaimsIdentity(

claims, CookieAuthenticationDefaults.

AuthenticationScheme);

var principal = new ClaimsPrincipal(identity);

var props = new AuthenticationProperties();

props.IsPersistent = model.RememberMe;

HttpContext.SignInAsync(

CookieAuthenticationDefaults.

AuthenticationScheme,

principal, props).Wait();

return RedirectToAction("Index", "Home");

}

else

{

ViewData["message"] = "Invalid UserName

or Password!";

}

return View();

}

Login() 的 POST 版本对我们来说很重要,因为正是在这里向用户颁发身份验证 cookie。

代码首先判断用户名和密码是否有效。这是通过检查是否存在与给定用户名和密码匹配的 MyAppUser 实体来实现的。如果存在匹配的用户名和密码,则将 isUserValid 布尔变量赋值为 true 或 false。

如果用户有效,则会创建四个对象:

  • 索赔对象列表
  • ClaimsIdentity 对象
  • ClaimsPrincipal 对象
  • 身份验证属性对象

声明对象列表保存了用户的所有声明。我们添加的第一个声明对象类型为 Name,表示用户的用户名。此声明是必需的,以便 HttpContext.User.Identity.Name 属性返回当前用户的用户名。

然后,我们为用户添加一系列角色。这是通过拆分 MyAppUser 的 Roles 属性,然后添加 Role 类型的 Claim 对象来实现的。这样做是为了确保 HttpContext.User.IsInRole() 方法能够按预期工作。

稍后在 HomeController 中,我们将使用 HttpContext.User.Identity.Name 和 HttpContext.User.IsInRole()。

然后,代码通过传递 Claim 对象列表和身份验证方案名称来创建 ClaimsIdentity 对象。

然后,代码通过在构造函数中传递 ClaimsIdentity 来创建一个 ClaimsPrincipal 对象。

然后,代码会创建一个 AuthenticationProperties 对象。AuthenticationProperties 对象保存着当前身份验证会话使用的某些属性的值,例如 IsPersistent。

最后,调用 HttpContext 的 SignInAsync() 方法向用户颁发身份验证 cookie。SignInAsync() 方法接受身份验证方案名称、ClaimsPrincipal 和 AuthenticationProperties 这三个参数。

用户成功登录后,响应将重定向到 HomeController 的 Index() 操作。您也可以使用 Login() 操作的 returnUrl 参数来实现重定向。

如果用户无效,则会相应地设置 ViewData 消息。

从应用程序中注销

下面显示的是移除身份验证 cookie 的 Logout() 操作:

复制代码
[HttpPost]
public IActionResult Logout()
{
    HttpContext.SignOutAsync(
CookieAuthenticationDefaults.AuthenticationScheme);
    return RedirectToAction("Login", "Account");
}

Logout() 操作会调用 HttpContext 的 SignOutAsync() 方法,并传入身份验证方案名称。此方法会移除身份验证 cookie。之后,用户将被重定向到登录页面。

创建注册和登录视图

好的,目前为止一切顺利。现在我们来创建视图。在"视图">"帐户"文件夹下添加两个视图:Register.cshtml 和 Login.cshtml。

这是浏览器中"注册"页面的显示效果:

下面给出的是创建登录视图的标记:

@model SimpleCookieAuth.ViewModels.RegisterViewModel

<h1>Register</h1>

<form asp-controller="Account" asp-action="Register"

method="post">

<table>

<tr>

<td><label asp-for="UserName"></label></td>

<td><input asp-for="UserName" /></td>

</tr>

<tr>

<td><label asp-for="Password"></label></td>

<td><input asp-for="Password"

type="password" /></td>

</tr>

<tr>

<td><label asp-for="ConfirmPassword"></label></td>

<td><input asp-for="ConfirmPassword"

type="password"/></td>

</tr>

<tr>

<td colspan="2">

<input type="submit"

value="Register" />

</td>

</tr>

</table>

<div asp-validation-summary="All"></div>

<br />

<div>@ViewData["message"]</div>

</form>

表单标签助手会将表单提交到 AccountController 的 Login() 操作。登录页面包含用于输入用户名和密码的文本框,以及一个用于指示是否记住登录状态的复选框。

至此,注册和登录界面已完成。

创建 HomeController 和 Index 视图

现在添加 HomeController 并按如下所示修改 Index() 操作:

Authorize

public IActionResult Index()

{

string userName = HttpContext.User.Identity.Name;

if(HttpContext.User.IsInRole("Administrator"))

{

ViewData["adminMessage"] = "You are an Administrator!";

}

if (HttpContext.User.IsInRole("Manager"))

{

ViewData["managerMessage"] = "You are a Manager!";

}

ViewData["username"] = userName;

return View();

}

请注意以粗体字标记的代码。Index() 操作带有 [Authorize] 属性,表明这是一个安全操作,只能由已认证的用户调用。

HttpContext.User.Identity.Name 属性返回当前用户的名称。请记住,我们之前在 Login() 操作中添加了一个类型为 Name 的 Claim 对象,以使该属性能够按预期工作。

如果当前用户属于指定的角色,则 HttpContext.User.IsInRole() 方法返回 true。请记住,我们之前在 Login() 操作中添加了 Role 类型的 Claim 对象,以使此方法按预期工作。

索引视图的示例运行会产生以下输出:

下面显示的是索引视图背后的标记:

<h1>Welcome @ViewData["username"]!</h1>

<h2>@ViewData["adminMessage"]</h2>

<h2>@ViewData["managerMessage"]</h2>

<form asp-controller="Account" asp-action="Logout"

method="post">

<input type="submit" value="Logout" />

</form>

这段标记代码只是简单地输出之前添加的各种 ViewData 条目,并在底部渲染"注销"按钮。点击"注销"按钮会触发 AccountController 的 Logout() 操作。

示例应用程序到此完成。运行该应用程序。您会注意到,尽管您尝试访问 HomeController 的 Index() 操作,但系统却跳转到了登录页面。您还会看到 RetrnUrl 查询字符串参数指向 Web 应用程序的根目录。使用登录页面底部的"创建用户"链接进入注册视图。创建一个新的用户帐户并尝试使用该帐户登录。同时,检查"记住我"复选框是否正常工作。

您可以点击此处阅读更多关于cookie身份验证的内容 。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。

相关推荐
To Be Clean Coder3 小时前
【Spring源码】createBean如何寻找构造器(二)——单参数构造器的场景
java·后端·spring
你才是臭弟弟3 小时前
SpringBoot 集成MinIo(根据上传文件.后缀自动归类)
java·spring boot·后端
C澒3 小时前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流
鸣潮强于原神4 小时前
TSMC chip_boundary宽度规则解析
后端
Code blocks4 小时前
kingbase数据库集成Postgis扩展
数据库·后端
Elieal4 小时前
JWT 登录校验机制:5 大核心类打造 Spring Boot 接口安全屏障
spring boot·后端·安全
czlczl200209254 小时前
Spring Boot Filter :doFilter 与 doFilterInternal 的差异
java·spring boot·后端
码界奇点4 小时前
基于Spring Boot和Activiti6的工作流OA系统设计与实现
java·spring boot·后端·车载系统·毕业设计·源代码管理
csdn_aspnet4 小时前
.Net Core — Cookie 身份验证
.netcore·cookie
yangminlei4 小时前
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
java·spring boot·后端