您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > .NET

从零开始学ASP.NET Core Identity框架

时间:2020-11-10 10:24:11  来源:  作者:

我们经常使用的各类网站App均会涉及注册、登录和修改密码等功能,登录系统后,有些功能会提示没有权限,甚至有些位置我们无法访问,这些都是系统权限和认证的体现。

我们从本章及后面的章节中,将学习在ASP.NET Core应用程序中使用ASP.NET Core Identity实现安全认证相关功能所需要掌握的知识。

本章主要向读者介绍如下内容。

  • 什么是ASP.NET Core Identity。
  • 如何在系统中启用Identity服务。
  • UserManager与SignInManager的API介绍及使用。
  • 登录用户的Cookie管理。

21.1 ASP.NET Core Identity介绍

ASP.NET Core Identity是一个会员身份系统,早期它的名字是Membership,当然那是一段“古老”的历史,现在我们来了解全新的Identity。它允许我们创建、读取、更新和删除账户。支持账号验证、身份验证、授权、恢复密码和SMS双因子身份验证。它还支持微软、Facebook和google等第三方登录提供商。它提供了一个丰富的API,并且这些API还可以进行大量的扩展。我们将在本书的后面实现这些功能。

添加ASP.NET Core Identity服务

这里采用的是EF Core,因为要让我们的系统支持Identity服务,所以需要安装它的程序包。打开NuGet管理器,安装Microsoft.AspNetCore.Identity.EntityFrameworkCore即可。

以下是添加和配置ASP.NET Core Identity服务的步骤。

使AppDbContext继承类IdentityDbContext,然后引入命名空间,代码如下。

public class AppDbContext:IdentityDbContext
{
   //其余代码
}
  • 应用程序AppDbContextDbContext类必须继承IdentityDbContext类而不是DbContext类。
  • 因为IdentityDbContext提供了管理SQL Server中的Identity表所需的所有DbSet属性,所以将看到ASP.NET Core Identity框架中要生成的所有数据库表。
  • 如果浏览IdentityDbContext类的定义(按F12键可以看到),则将看到它继承自DbContext类。因此,如果类继承自IdentityDbContext类,那么不必显式继承DbContext类。

配置ASP.NET Core Identity服务。在Startup类的ConfigureServices()方法中,添加以下代码行。

services.AddIdentity<IdentityUser,IdentityRole>()
        .AddEntityFrameworkStores<AppDbContext>();
  • AddIdentity()方法是指为系统提供默认的用户和角色类型的身份验证系统。
  • IdentityUser类由ASP.NET Core提供,包含UserName、PasswordHash和Email等属性。这是ASP.NET Core Identity框架默认使用的类,用于管理应用程序的注册用户。
  • 如果读者希望存储有关注册用户的其他信息,比如性别、城市等,则需要创建一个派生自IdentityUser的自定义类。在此自定义类中添加所需的其他属性,然后插入此类而不是内置的IdentityUser类。我们将在后面的章节中学习如何执行此操作。
  • 同样,IdentityRole也是ASP.NET Core Identity提供的内置类,包含角色信息。
  • 使用EntityFrameWork Core从基础SQL Server数据库存储和查询注册用户的角色信息。
  • 使用AddEntityFrameworkStores()方法,然后指定DbContext类作为泛型参数。

接下来,将Authentication()中间件添加到请求管道,代码如下。

    public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
        {
            //如果环境是Development serve Developer Exception Page
            if(env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //否则显示用户友好的错误页面
            else if(env.IsStaging() || env.IsProduction() || env.IsEnvironment("UAT"))
            {
                app.UseExceptionHandler("/Error");
                app.UseStatusCodePagesWithReExecute("/Error/{0}");
            }

            //使用纯静态文件支持的中间件,而不使用带有终端的中间件
            app.UseStaticFiles();
            //添加验证中间件
            app.UseAuthentication();

            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name:"default",
                    pattern:"{controller=Home}/{action=Index}/{id?}");
            });
        }

在Startup类的Configure()方法中,调用UseAuthentication()方法将Authentication()中间件添加到应用程序的请求处理管道中。我们希望能够在请求到达MVC中间件之前对用户进行身份验证。因此,在请求处理管道的UseRouting()中间件之前添加认证中间件。这很重要,因为我们之前讲过中间件的添加顺序不能乱。

现在开始添加身份迁移。在Visual Studio中的程序包控制台窗口执行以下命令以添加新迁移。

Add-Migration AddingIdentity

此迁移包含用于创建ASP.NET Core Identity系统所需的表的代码。

如果运行,则会出现以下错误。

The entity type'IdentityUserLogin'requires a primary key to be defined.

之前因为要封装Seed()方法,所以重写OnModelCreating()方法。出现这个错误是因为我们在DbContext类中重写了OnModelCreating()方法,但未调用基本IdentityDbContext类OnModelCreating()方法。

Identity表的键映射在IdentityDbContext类的OnModelCreating()方法中。因此,要解决这个错误,需要做的是,调用基类OnModelCreating()使用该方法的基础关键字,代码如下。

   public class AppDbContext:IdentityDbContext
    {

        public AppDbContext(DbContextOptions<AppDbContext> options):base(options)
        {
        }
        public DbSet<Student> Students{get;set;}
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Seed();
        }
    }

执行Update-Database命令以应用迁移记录并创建所需的身份表,如图21.1所示。

从零开始学ASP.NET Core Identity框架

 

图21.1

21.2 使用ASP.NET Core Identity注册新用户

现在已经创建好了表的信息,接下来我们增加一个注册功能,让用户能够注册到系统中。

新用户注册视图应如图21.2所示。为了能够注册为新用户,需要邮箱地址和密码两个字段。

从零开始学ASP.NET Core Identity框架

 

图21.2

21.2.1 RegisterViewModel视图模型

我们将使用RegisterViewModel类作为Register视图的模型,它负责将视图中的信息传递给控制器。为了验证信息是否正确,我们使用了几个ASP.NET Core验证属性。在之前的章节中详细说明过这些属性和模型验证。

using System.ComponentModel.DataAnnotations;


namespace MockSchoolManagement.ViewModels
{
    public class RegisterViewModel
    {

        [Required]
        [EmailAddress]
        [Display(Name = "邮箱地址")]

        public string Email{get;set;}

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "密码")]

        public string Password{get;set;}

        [DataType(DataType.Password)]
        [Display(Name = "确认密码")]
        [Compare("Password",
            ErrorMessage = "密码与确认密码不一致,请重新输入.")]
        public string ConfirmPassword{get;set;}
    }
}

在这里我们添加了DataType特性,它的主要作用是指定比数据库内部类型更具体的数据类型。DataType枚举提供了多种数据类型,比如日期、时间、电话号码、货币和邮箱地址等。但是请注意,DataType特性不提供任何验证,它主要服务于我们的视图文件,比如,DataType.EmailAddress可以在视图中创建mailto:链接,DataType.Date则会在支持html5的浏览器中提供日期选择器。

21.2.2 账户控制器

账户控制器(AccountController)是指所有与账户相关的CRUD(增加、读取、更新和删除)操作都将在此控制器中。目前我们只有Register()操作方法,可以通过向/account/register发出GET请求来实现此操作方法。

using Microsoft.AspNetCore.Mvc;

namespace MockSchoolManagement.Controllers
{
    public class AccountController:Controller
    {
        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }
    }
}

21.2.3 注册视图中的代码

将此视图放在Views/Account文件夹中,此视图的模型是我们在前面创建的Register ViewModel。

@model RegisterViewModel
@{ViewBag.Title = "用户注册";}

<h1>用户注册</h1>

<div class="row">
  <div class="col-md-12">
    <form method="post">
      <div asp-validation-summary="All" class="text-danger"> </div>
      <div class="form-group">
        <label asp-for="Email"> </label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"> </span>
      </div>
      <div class="form-group">
        <label asp-for="Password"> </label>
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"> </span>
      </div>
      <div class="form-group">
        <label asp-for="ConfirmPassword"> </label>
        <input asp-for="ConfirmPassword" class="form-control" />
        <span asp-validation-for="ConfirmPassword" class="text-danger"> </span>
      </div>
      <button type="submit" class="btn btn-primary">注册</button>
    </form>
  </div>
</div>

21.2.4 添加注册按钮

在布局视图中添加注册按钮,我们需要在_Layout.cshtml文件中找到ID为collapsibleNavbar的导航菜单栏,在下方添加注册按钮,导航到对应的视图,代码如下。

<div id="collapsibleNavbar" class="collapse navbar-collapse">
  <ul class="navbar-nav">
    <li class="nav-item">
      <a class="nav-link" asp-controller="home" asp-action="Index">学生列表</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" asp-controller="home" asp-action="Create">添加学生</a>
    </li>
  </ul>
  <ul class="navbar-nav ml-auto">
    <li class="nav-item">
      <a class="nav-link" asp-controller="account" asp-action="register"> 注册 </a>
    </li>
  </ul>
</div>

运行项目后,单击注册按钮即可看到图21.2所示的效果图,接下来我们实现处理HttpPOST请求到/account/register的Register()操作方法。然后通过表单Taghelpers将数据发布到ASP.NET Core Identity中创建账户。

21.3 UserManager和SignInManager服务

在本节我们学习使用ASP.NET Core Identity提供的UserManager服务创建新用户,然后使用其提供的SignInManager服务来登录用户。

UserManager <IdentityUser>类包含管理基础数据存储中的用户所需的方法。比如,此类具有CreateAsync()、DeleteAsync()和UpdateAsync()等方法来创建、删除和更新用户,如图21.3所示。

从零开始学ASP.NET Core Identity框架

 

图21.3

SignInManager <IdentityUser>类包含用户登录所需的方法。比如,SignInManager类具有SignInAsync()、SignOutAsync()等方法来登录和注销用户,如图21.4所示。

  • UserManager和SignInManager服务都需要使用构造函数注入AccountController,并且这两个服务都接收泛型参数。
  • 这些服务接收泛型参数的User类。目前,我们使用内置的IdentityUser类作为泛型参数的参数。
  • 这两个服务的通用参数User是一个扩展类。这意味着,我们可以自定义与用户有关的信息和其他数据,来创建我们的自定义用户。
从零开始学ASP.NET Core Identity框架

 

图21.4

  • 我们可以声明自己的自定义类作为泛型参数,而不是内置的IdentityUser类。

以下是AccountController的完整代码。

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MockSchoolManagement.ViewModels;
using System.Threading.Tasks;

namespace MockSchoolManagement.Controllers
{
    public class AccountController:Controller
    {
        private UserManager<IdentityUser> _userManager;
        private SignInManager<IdentityUser> _signInManager;

        public AccountController(UserManager<IdentityUser> userManager,
          SignInManager<IdentityUser> signInManager)
        {
            this._userManager = userManager;
            this._signInManager = signInManager;
        }

        [HttpGet]
        public IActionResult Register()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Register(RegisterViewModel model)
        {
            if(ModelState.IsValid)
            {
                //将数据从RegisterViewModel复制到IdentityUser
                var user = new IdentityUser
                {
                    UserName = model.Email,
                    Email = model.Email
                };

                //将用户数据存储在AspNetUsers数据库表中
                var result = await _userManager.CreateAsync(user,model.Password);

                //如果成功创建用户,则使用登录服务登录用户信息
                //并重定向到HomeController的索引操作
                if(result.Succeeded)
                {
                    await _signInManager.SignInAsync(user,isPersistent:false);
                    return RedirectToAction("index","home");
                }

                //如果有任何错误,则将它们添加到ModelState对象中
                //将由验证摘要标记助手显示到视图中
                foreach(var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty,error.Description);
                }
            }

            return View(model);
        }
    }
}

此时,如果读者运行项目并提供有效的邮箱地址和密码,则它会在SQL Server数据库的AspNetUsers表中创建账户。读者可以从Visual Studio的SQL Server对象资源管理器中查看此数据,如图21.5所示。

从零开始学ASP.NET Core Identity框架

 

图21.5

21.3.1 ASP.NET Core Identity中对密码复杂度的处理

在刚刚注册的时候,我们发现有两个问题。

  • 密码验证机制太复杂了。
  • 它是英文的,对于我们来说支持不是很友好。

这是因为ASP.NET Core IdentityOptions类在ASP.NET Core中用于配置密码复杂性规则。默认情况下,ASP.NET Core身份不允许创建简单的密码来保护我们的应用程序免受自动暴力攻击。

当我们尝试使用像abc这样的简单密码注册新账户时,会显示创建失败,读者将看到如图21.6所示的验证错误。

从零开始学ASP.NET Core Identity框架

 

图21.6

我们在图21.6中看到中文提示,后面的章节会告诉读者如何配置。

21.3.2 ASP.NET Core Identity密码默认设置

在ASP.NET Core Identity中,密码默认设置在PasswordOptions类中。读者可以在ASP.NET Core GitHub仓库中找到此类的源代码。只需在仓库中搜索PasswordOptions类。

代码如下。

public class PasswordOptions
{
    public int RequiredLength{get;set;} = 6;
    public int RequiredUniqueChars{get;set;} = 1;
    public bool RequireNonAlphanumeric{get;set;} = true;
    public bool RequireLowercase{get;set;} = true;
    public bool RequireUppercase{get;set;} = true;
    public bool RequireDigit{get;set;} = true;
}

相关参数的说明如表21.1(略)所示。

21.3.3 覆盖ASP.NET Core身份中的密码默认设置

我们可以通过在Startup类的ConfigureServices()方法中使用IServiceCollection接口的Configure()方法来实现这一点。

services.Configure<IdentityOptions>(options =>
{
     options.Password.RequiredLength = 6;
                options.Password.RequiredUniqueChars = 3;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireLowercase = false;
                options.Password.RequireUppercase = false;

});

也可以在添加身份服务时执行此操作,代码如下。

services.AddIdentity<IdentityUser,IdentityRole>(options =>
{
    options.Password.RequiredLength = 6;
    options.Password.RequiredUniqueChars = 3;
    options.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<AppDbContext>();

当然,在这里推荐使用IdentityOptions的形式进行配置,因为它可以作为一个独立服务,而不是嵌套在AddIdentity()方法中。

IdentityOptions对象中除了Password的配置信息,还有用户、登录、策略等配置信息,我们可以根据不同的场景进行灵活的配置。

  • UserOptions。
  • SignInOptions。
  • LockoutOptions。
  • TokenOptions。
  • StoreOptions。
  • ClaimsIdentityOptions。

21.3.4 修改中文提示的错误信息

Identity提供了AddErrorDescriber()方法,可方便我们进行错误内容的配置和处理。

ASP.NET Core默认提供的都是英文提示,我们可以将它们修改为中文。现在我们创建一个CustomIdentityErrorDescriber的类文件,路径为根目录下创建的CustomerMiddlewares文件夹,然后继承IdentityErrorDescriber服务,添加以下代码。

  public class CustomIdentityErrorDescriber:IdentityErrorDescriber
    {

        public override IdentityError DefaultError()
        {
            return new IdentityError{Code = nameof(DefaultError),Description = $"发生了未知的故障。" };
        }

        public override IdentityError ConcurrencyFailure()
        {
            return new IdentityError{Code = nameof(ConcurrencyFailure),Description = "乐观并发失败,对象已被修改。" };
        }

        public override IdentityError PasswordMismatch()
        {
            return new IdentityError{Code = nameof(PasswordMismatch),Description = "密码错误" };
        }

        public override IdentityError InvalidToken()
        {
            return new IdentityError{Code = nameof(InvalidToken),Description = "无效的令牌." };
        }

        public override IdentityError LoginAlreadyAssociated()
        {
            return new IdentityError{Code = nameof(LoginAlreadyAssociated),Description = "具有此登录的用户已经存在." };
        }

        public override IdentityError InvalidUserName(string userName)
        {
            return new IdentityError{Code = nameof(InvalidUserName),Description = $"用户名'{userName}'无效,只能包含字母或数字." };
        }

        public override IdentityError InvalidEmail(string email)
        {
            return new IdentityError{Code = nameof(InvalidEmail),Description = $"邮箱'{email}'无效." };
        }

        public override IdentityError DuplicateUserName(string userName)
        {
            return new IdentityError{Code = nameof(DuplicateUserName),Description = $"用户名'{userName}'已被使用." };
        }

        public override IdentityError DuplicateEmail(string email)
        {
            return new IdentityError{Code = nameof(DuplicateEmail),Description = $"邮箱'{email}'已被使用." };
        }

        public override IdentityError InvalidRoleName(string role)
        {
            return new IdentityError{Code = nameof(InvalidRoleName),Description = $"角色名'{role}'无效." };
        }

        public override IdentityError DuplicateRoleName(string role)
        {
            return new IdentityError{Code = nameof(DuplicateRoleName),Description = $"角色名'{role}'已被使用." };
        }

        public override IdentityError UserAlreadyHasPassword()
        {
            return new IdentityError{Code = nameof(UserAlreadyHasPassword),Description = "该用户已设置了密码." };
        }

        public override IdentityError UserLockoutNotEnabled()
        {
            return new IdentityError{Code = nameof(UserLockoutNotEnabled),Description = "此用户未启用锁定." };
        }

        public override IdentityError UserAlreadyInRole(string role)
        {
            return new IdentityError{Code = nameof(UserAlreadyInRole),Description = $"用户已关联角色'{role}'." };
        }
        public override IdentityError UserNotInRole(string role)
        {
            return new IdentityError{Code = nameof(UserNotInRole),Description = $"用户未关联角色'{role}'." };
        }

        public override IdentityError PasswordTooShort(int length)
        {
            return new IdentityError{Code = nameof(PasswordTooShort),Description = $"密码必须至少是{length}字符." };
        }

        public override IdentityError PasswordRequiresNonAlphanumeric()
        {
            return new IdentityError
            {
                Code = nameof(PasswordRequiresNonAlphanumeric),
                Description = "密码必须至少有一个非字母数字字符."
            };
        }

        public override IdentityError PasswordRequiresDigit()
        {
            return new IdentityError{Code = nameof(PasswordRequiresDigit),Description = $"密码必须至少有一个数字('0'-'9')." };
        }


        public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)
        {
            return new IdentityError{Code = nameof(PasswordRequiresUniqueChars),Description = $"密码必须使用至少不同的{uniqueChars}字符。" };
        }

        public override IdentityError PasswordRequiresLower()
        {
            return new IdentityError{Code = nameof(PasswordRequiresLower),Description = "密码必须至少有一个小写字母('a'-'z')." };
        }

        public override IdentityError PasswordRequiresUpper()
        {
            return new IdentityError{Code = nameof(PasswordRequiresUpper),Description = "密码必须至少有一个大写字母('A'-'Z')." };
        }



    }

回到Startup类的ConfigureServices()方法中,在AddIdentity()服务中使用AddErrorDescriber()方法覆盖默认的错误提示内容,代码如下。

 services.AddIdentity<IdentityUser,IdentityRole>().AddErrorDescriber<CustomIdentityErrorDescriber>().AddEntityFrameworkStores<AppDbContext>();

配置完成之后,提示变为中文,注册时密码长度达到6位即可。

21.4 登录状态及注销功能的实现

在本节中我们学习如何判断用户是否登录,以及注册、登录和注销等功能是否可实现。

首先来看一看如何在ASP.NET Core中实现注销功能。如果用户未登录,则显示登录和注册按钮,如图21.7所示。

从零开始学ASP.NET Core Identity框架

 

图21.7

如果用户已登录,请隐藏登录和注册按钮并显示注销按钮,如图21.8所示。

从零开始学ASP.NET Core Identity框架

 

图21.8

我们需要在_Layout.cshtml文件中找到ID为collapsibleNavbar的导航菜单栏,修改代码如下。

在下方代码中注入了SignInManager,以便我们检查用户是否已登录,来决定显示和隐藏的内容。

@using Microsoft.AspNetCore.Identity @inject SignInManager<IdentityUser>
  _signInManager
  <div class="collapse navbar-collapse" id="collapsibleNavbar">
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" asp-controller="home" asp-action="index">学生列表</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" asp-controller="home" asp-action="create">添加学生</a>
      </li>
    </ul>
    <ul class="navbar-nav ml-auto">
      @*如果用户已登录,则显示注销链接*@ @if(_signInManager.IsSignedIn(User)) {
      <li class="nav-item">
        <form method="post" asp-controller="account" asp-action="logout">
          <button type="submit" style="width:auto"
            class="nav-link btn btn-link py-0">
            注销 @User.Identity.Name
          </button>
        </form>
      </li>
      }else{
      <li class="nav-item">
        <a class="nav-link" asp-controller="account" asp-action="register">
          注册
        </a>
      </li>
      <li class="nav-item">
        <a class="nav-link" asp-controller="account" asp-action="login">
          登录
        </a>
      </li>
      }
    </ul>
  </div>
</IdentityUser>

然后在AccountController中添加以下Logout()方法。

        [HttpPost]
        public async Task<IActionResult> Logout()
        {
            await _signInManager.SignOutAsync();
            return RedirectToAction("index","home");
        }

请注意,我们使用POST请求将用户注销,而不使用GET请求,因为该方法可能会被滥用。恶意者可能会诱骗用户单击某张图片,将图片的src属性设置为应用程序注销URL,这样会造成用户在不知不觉中退出了账户。

21.5 ASP.NET Core Identity中的登录功能实现

在本节中,我们将讨论使用ASP.NET Core Identity的API在ASP.NET Core应用程序中实现登录功能。要在ASP.NET Core应用程序中实现登录功能,我们需要实现以下功能。

  • 登录视图模型。
  • 登录视图。
  • AccountController中的两个Login()操作方法。

21.5.1 LoginViewModel登录视图模型

要在系统中登录用户,则需要其邮箱、用户名、密码以及使其选择是否需要持久性Cookie或会话Cookie。

 public class LoginViewModel
    {

        [Required]
        [EmailAddress]
        public string Email{get;set;}

        [Required]
        [DataType(DataType.Password)]
        public string Password{get;set;}

        [Display(Name = "记住我")]
        public bool RememberMe{get;set;}
    }

21.5.2 登录视图的代码

登录视图的代码如下。

@model LoginViewModel
@{ViewBag.Title = "用户登录";}

<h1>用户登录</h1>

<div class="row">
  <div class="col-md-12">
    <form method="post">
      <div asp-validation-summary="All" class="text-danger"> </div>
      <div class="form-group">
        <label asp-for="Email"> </label>
        <input asp-for="Email" class="form-control" />
        <span asp-validation-for="Email" class="text-danger"> </span>
      </div>
      <div class="form-group">
        <label asp-for="Password"> </label>
        <input asp-for="Password" class="form-control" />
        <span asp-validation-for="Password" class="text-danger"> </span>
      </div>
      <div class="form-group">
        <div class="checkbox">
          <label asp-for="RememberMe">
            <input asp-for="RememberMe" />
            @Html.DisplayNameFor(m => m.RememberMe)
          </label>
        </div>
      </div>
      <button type="submit" class="btn btn-primary">登录</button>
    </form>
  </div>
</div>

21.5.3 AccountController中的Login()操作方法

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MockSchoolManagement.ViewModels;
using System.Threading.Tasks;

namespace MockSchoolManagement.Controllers
{
    public class AccountController:Controller
    {
       private UserManager<IdentityUser> _userManager;
        private SignInManager<IdentityUser> _signInManager;

        public AccountController(UserManager<IdentityUser> userManager,
          SignInManager<IdentityUser> signInManager)
        {
            this._userManager = userManager;
            this._signInManager = signInManager;
        }

        [HttpGet]
        public IActionResult Login()
        {
            return View();
        }

        [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if(ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(
                    model.Email,model.Password,model.RememberMe,false);

                if(result.Succeeded)
                {

                        return RedirectToAction("index","home");
                }

                ModelState.AddModelError(string.Empty,"登录失败,请重试");
            }

            return View(model);
        }

    }
}

21.5.4 会话Cookie与持久性Cookie

维基百科解释:Cookie并不是它的原意“甜饼”的意思,而是一个保存在客户机中的简单的文本文件,这个文件与特定的Web文档关联在一起,保存了该客户机访问这个Web文档时的信息,当客户机再次访问这个Web文档时这些信息可供该文档使用。由于“Cookie”具有可以保存在客户机上的神奇特性,因此它可以帮助我们实现记录用户个人信息的功能,而这一切都不必使用复杂的CGI等程序。

简单来说,我们把Cookie理解为一个大小不超过4kB,便于我们在客户端保存一些用户个人信息的功能。

在ASP.NET Core Identity中,用户成功登录后,将发出Cookie,并将此Cookie随每个请求一起发送到服务器,服务器会解析此Cookie信息来了解用户是否已经通过身份验证和登录。此Cookie可以是会话Cookie或持久Cookie。

会话Cookie是指用户登录成功后,Cookie会被创建并存储在浏览器会话实例中。会话Cookie不包含过期时间,它会在浏览器窗口关闭时被永久删除。

持久Cookie是指用户登录成功后,Cookie会被创建并存储在浏览器中,因为是持久Cookie,所以在关闭浏览器窗口后,它不会被删除。但是,它通常有一个到期时间,会在到期后被删除。

在LoginViewModel.cs视图模型中,我们已经添加了一个bool类型的RememberMe属性。用户可在登录时选择记住我,选中即使用持久性Cookie,而未选中则为会话Cookie。

现在运行项目,我们可以在登录的时候选择记住我,登录成功后如图21.9所示。

从零开始学ASP.NET Core Identity框架

 

图21.9

打开开发者工具(按F12键),观察图21.9框中的内容,可以发现过期时间是很长的。现在关闭浏览器,并将其再次打开,用户也依然是登录状态。这便是持久性Cookie的作用,只有在到期时间到了之后才会删除。

至于会话Cookie验证,我们在登录的时候取消选择记住我,然后看到如图21.10所示的内容。

从零开始学ASP.NET Core Identity框架

 

图21.10

这里已经是一个会话了,它不包含过期时间,在关闭浏览器后,再次将其打开,系统会自动注销用户。

以上就是持久性Cookie与会话Cookie的区别了。

在本章中我们学习了Identity的基本功能,创建一个系统用户并完成了登录注册及状态检查。在后面的章节中,内容会逐步深入,可配合源代码学习。

21.6 小结

本章介绍了ASP.NET Core Identity框架的定位及作用,并利用它提供的API完成了用户的登录与注销等基本功能。在后面的章节中我们会使用更多的API将系统趋于完善。

本文摘自《深入浅出 ASP.NET Core》



Tags:.NET   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
编译和反编译.NET 中的编译是把开发人员写的 C# 代码转化为计算机可理解的代码的过程,也就是中间语言代码(IL代码)。在这个过程中,C# 源代码被转换为可执行文件(exe或者dll 文件)...【详细内容】
2022-07-15  Tags: .NET  点击:(1)  评论:(0)  加入收藏
我们在开发 webapi 项目时如果遇到 api 接口需要同时支持多个版本的时候,比如接口修改了入参之后但是又希望支持老版本的前端(这里的前端可能是网页,可能是app,小程序 等等)进行...【详细内容】
2022-07-14  Tags: .NET  点击:(3)  评论:(0)  加入收藏
什么是.NET.NET 是由 Microsoft 创建的开源开发平台,用于生成多种不同类型的应用程序,主要支持C#、F#及VB。.NET程序运行原理.NET程序的运行是由其虚拟机CLR(公共语言运行时)把...【详细内容】
2022-06-21  Tags: .NET  点击:(26)  评论:(0)  加入收藏
项目介绍一套基于.NetCore+Vue2/Vue3+Element plus+uniapp,采用前后端分离方式,全自动生成PC与移动端代码的快速开发脚手架;支持移动端(uniapp)ios/android/h5/微信小程序。 Vu...【详细内容】
2022-06-13  Tags: .NET  点击:(56)  评论:(0)  加入收藏
多年前借鉴b/s优势实现基于.net的c/s框架 本框架未采用.NET固有的WCF通讯解决方案,而使用传统http数据加密方式.框架初始和传统客户端升级方式一样检查新版本,如有则下载升级...【详细内容】
2022-06-05  Tags: .NET  点击:(30)  评论:(0)  加入收藏
Asp.Net Core Identity 是.Net自带的身份认证系统,支持用户界面 (UI) 登录功能,并且管理用户、密码、配置文件数据、角色、声明、令牌、电子邮件确认等等。使用Visual Studio...【详细内容】
2022-06-05  Tags: .NET  点击:(35)  评论:(0)  加入收藏
安装Hangfire新建ASP.NET Core空 项目,.Net Core版本3.1 往*.csproj添加包引用,添加新的PackageReference标记。如下所示。请注意,下面代码段中的版本可能已经过时,如有需要,请使...【详细内容】
2022-05-07  Tags: .NET  点击:(76)  评论:(0)  加入收藏
 B/S架构的Web程序几乎占据了应用软件的绝大多数市场,但是C/S架构的WinForm、WPF客户端程序依然具有很实用的价值,如设计类软件 AutoCAD与Autodesk Revit、WPS、IT类的集成开...【详细内容】
2022-04-27  Tags: .NET  点击:(153)  评论:(0)  加入收藏
前几天有个老项目找到我,有多老呢?比我工作年限都长,见到这个项目我还得叫一声前辈。这个项目目前使用非常稳定,十多年了没怎么更新过,现在客户想加一个小功能:在线预览Word文档。...【详细内容】
2022-04-27  Tags: .NET  点击:(65)  评论:(0)  加入收藏
之前,我们已经了解了ASP.NET Core中的身份认证,现在,我们来聊一下授权。老规矩,示例程序源码XXTk.Auth.Samples已经提交了,需要的请自取。概述ASP.NET Core中的授权方式有很多,我...【详细内容】
2022-04-20  Tags: .NET  点击:(143)  评论:(0)  加入收藏
▌简易百科推荐
编译和反编译.NET 中的编译是把开发人员写的 C# 代码转化为计算机可理解的代码的过程,也就是中间语言代码(IL代码)。在这个过程中,C# 源代码被转换为可执行文件(exe或者dll 文件)...【详细内容】
2022-07-15  IT狂人日记    Tags:.NET   点击:(1)  评论:(0)  加入收藏
我们在开发 webapi 项目时如果遇到 api 接口需要同时支持多个版本的时候,比如接口修改了入参之后但是又希望支持老版本的前端(这里的前端可能是网页,可能是app,小程序 等等)进行...【详细内容】
2022-07-14  IT技术资源爱好者    Tags:.Net   点击:(3)  评论:(0)  加入收藏
什么是.NET.NET 是由 Microsoft 创建的开源开发平台,用于生成多种不同类型的应用程序,主要支持C#、F#及VB。.NET程序运行原理.NET程序的运行是由其虚拟机CLR(公共语言运行时)把...【详细内容】
2022-06-21  威步上海    Tags:.NET   点击:(26)  评论:(0)  加入收藏
Asp.Net Core Identity 是.Net自带的身份认证系统,支持用户界面 (UI) 登录功能,并且管理用户、密码、配置文件数据、角色、声明、令牌、电子邮件确认等等。使用Visual Studio...【详细内容】
2022-06-05  海椰人  博客园  Tags:.Net   点击:(35)  评论:(0)  加入收藏
安装Hangfire新建ASP.NET Core空 项目,.Net Core版本3.1 往*.csproj添加包引用,添加新的PackageReference标记。如下所示。请注意,下面代码段中的版本可能已经过时,如有需要,请使...【详细内容】
2022-05-07  壮志林云    Tags:.NET   点击:(76)  评论:(0)  加入收藏
 B/S架构的Web程序几乎占据了应用软件的绝大多数市场,但是C/S架构的WinForm、WPF客户端程序依然具有很实用的价值,如设计类软件 AutoCAD与Autodesk Revit、WPS、IT类的集成开...【详细内容】
2022-04-27  IT技术资源爱好者  博客园  Tags:.NET   点击:(153)  评论:(0)  加入收藏
前几天有个老项目找到我,有多老呢?比我工作年限都长,见到这个项目我还得叫一声前辈。这个项目目前使用非常稳定,十多年了没怎么更新过,现在客户想加一个小功能:在线预览Word文档。...【详细内容】
2022-04-27  海椰人  博客园  Tags:.Net   点击:(65)  评论:(0)  加入收藏
之前,我们已经了解了ASP.NET Core中的身份认证,现在,我们来聊一下授权。老规矩,示例程序源码XXTk.Auth.Samples已经提交了,需要的请自取。概述ASP.NET Core中的授权方式有很多,我...【详细内容】
2022-04-20  日行四善  博客园  Tags:授权   点击:(143)  评论:(0)  加入收藏
序言本文将分别介绍 Authentication(认证) 和 Authorization(授权)。并以简单的例子在 ASP.NET Core 6.0 的 WebAPI 中分别实现这两个功能。 相关名词Authentication 和 Author...【详细内容】
2022-04-18  芦荟柚子茶  博客园  Tags:ASP.NET   点击:(197)  评论:(0)  加入收藏
前言由于客户网络安全限制,连接到互联网的设备不能访问内网。需要先从客户端应用中导出数据到文件,再将文件复制到U盘,最后通过内网机器上传数据。如何保证,在复制、传输过程中,...【详细内容】
2022-03-22  My IO    Tags:.NET Core   点击:(125)  评论:(0)  加入收藏
站内最新
站内热门
站内头条