实用技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > ASP.NET > 实用技巧 > ASP.NET Core实现Cookie身份验证

在ASP.NET Core中实现Cookie身份验证的方法步骤

作者:hefeng_aspnet

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

如果您一直在使用 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 身份验证。

现在你已经对 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。

配置 Cookie 身份验证

好的。现在我们已经准备好了 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 将包含五个操作:

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

上述操作需要在构造函数中注入 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。

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

声明对象列表保存了用户的所有声明。我们添加的第一个声明对象类型为 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 应用程序的根目录。使用登录页面底部的“创建用户”链接进入注册视图。创建一个新的用户帐户并尝试使用该帐户登录。同时,检查“记住我”复选框是否正常工作。

以上就是在ASP.NET Core中实现Cookie身份验证的方法步骤的详细内容,更多关于ASP.NET Core实现Cookie身份验证的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文