十二、ASP.NET Core 认证

安全性对于所有类型的应用都是至关重要的,包括 web 应用。 如果有人可以冒充你来更新你的状态,你还会使用 Facebook 吗? 如果这是可能的,那么没有人会再回到 Facebook。 从这个示例中,我们可以看到,安全性与其说是一种特性,不如说是所有应用的必要特性。

在本章中,我们将学习以下主题:

  • 身份验证和授权
  • ASP。 净的身份
  • 如何在 ASP 中实现安全性。 asp.NET Core 应用使用.NET 身份与实体框架

当我们讨论应用的安全性时,我们主要希望防止任何未经授权的访问,这意味着只有访问信息的人才能够访问它——不多也不多。

在进一步讨论之前,我想澄清一些关于安全性的核心概念。

认证

身份验证是验证用户是否有权访问系统的过程。 在任何应用中,都将首先对用户进行身份验证。 这可以通过要求用户输入他们的用户 ID 和密码来实现。

授权

授权是验证用户是否有权访问所请求的资源的过程。 他们可能拥有对系统的合法访问权,但他们可能没有访问请求的资源的权限,因为他们没有必需的访问权。 例如,只有管理员用户可以访问应用的配置页面,而普通用户不应该被允许使用该页面。

ASP.NET Identity 为保护应用提供了几个特性。

让我们考虑以下简单场景,其中用户试图访问安全页面,该页面只有经过授权的人员才能访问。 由于用户没有登录,它们将被重定向到登录页面,以便我们可以对用户进行身份验证和授权。 认证成功后,用户将被重定向到安全页面。 如果由于任何原因,我们不能对用户进行身份验证和授权,我们可以将他们重定向到“拒绝访问”页面:

Authorization

ASP.NET Core Identity 是一个会员系统,它使您能够轻松地保护应用,并且具有向应用添加登录功能等特性。 以下是使用ASP 需要遵循的步骤.NET Identity(使用实体框架)

  1. 将相关依赖项添加到project.json文件中。
  2. 创建一个appsettings.json文件并存储数据库连接字符串。
  3. 创建一个ApplicationUser类和ApplicationDbContext类。
  4. 配置应用以使用 ASP。 净的身份。
  5. 创建用于注册和登录的 ViewModels。
  6. 创建必要的控制器和关联的操作方法和视图。

向项目中添加相关的依赖项。 json 文件

如果你想使用 ASP.NET Identity with Entity Framework 在你的应用中,你需要添加以下依赖:

"EntityFramework.Commands": "7.0.0-rc1-final", 
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final", 
    "Microsoft.AspNet.Authentication.Cookies": "1.0.0-rc1-final", 

创建一个appsettings.json文件并存储数据库连接字符串。

在项目的根级别创建一个名为appsettings.json的文件,如下截图所示:

Adding the relevant dependencies to the project.json file

将以下连接字符串存储在appsettings.json中。 这个连接字符串将被 ASP.NET Identity 将数据存储在相关的表中:

{ 
  "Data": { 
    "DefaultConnection": { 
      "ConnectionString": "Server=(localdb)\\mssqllocaldb;Database=aspnet_security;Trusted_Connection=True;MultipleActiveResultSets=true" 
   } 
  } 
} 

添加 ApplicationUser 和 ApplicationDbContext 类

创建一个Models文件夹和两个文件——ApplicationDbContext.csApplicationUser.cs -,如下截图所示:

Adding ApplicationUser and ApplicationDbContext classes

ApplicationUser类继承自IdentityUser类(在AspNet.Identity.EntityFramework6命名空间中可用),如下所示:

public class ApplicationUser : IdentityUser 
{
..  
} 

您可以根据应用的需要向用户添加属性。 我没有添加任何属性,因为我想保持事情简单,以显示 ASP 的功能。 净的身份。

ApplicationDbContext类继承自ApplicationUserIdentityDbContext类。 在构造函数方法中,传递connectionstring,最终传递给基类。

甚至OnModelCreating方法也被覆盖了。 如果你想改变任何表名(由 Identity 生成),你可以这样做:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 
    { 
        public ApplicationDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { } 

        protected override void OnModelCreating(DbModelBuilder modelBuilder) 
        { 
            base.OnModelCreating(modelBuilder);             
        } 
    } 

创建Models文件之后,需要配置应用和服务。 您可以在ConfigureConfigureServices中配置这些,它们在Startup类中找到。

配置应用使用身份

为了使用 Identity,我们只需要在Startup类的Configure方法中添加以下一行:

app.UseIdentity(); 

完整的Configure方法如下所示,同时调用UseIdentity方法,即app.UseIdentity():

public void Configure(IApplicationBuilder app, IHostingEnvironment env,  ILoggerFactory loggerFactory) 
        { 
            loggerFactory.AddConsole(Configuration.GetSection("Logging")); 
            loggerFactory.AddDebug(); 

            if (env.IsDevelopment()) 
            { 
                app.UseBrowserLink(); 
                app.UseDeveloperExceptionPage(); 
                app.UseDatabaseErrorPage(); 
            } 
            else 
            { 
                app.UseExceptionHandler("/Home/Error"); 

            app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear()); 

            app.UseStaticFiles(); 

            app.UseIdentity(); 

            // To configure external authentication please see http://go.microsoft.com/fwlink/?LinkID=532715 

            app.UseMvc(routes => 
            { 
                routes.MapRoute( 
                    name: "default", 
                    template: "{controller=Home}/{action=Index}/{id?}"); 
            }); 
        } 

ConfigureServices方法中,我们将做以下改动:

  • 我们将使用从appsettings.json文件获取的连接字符串添加ApplicationDbContext
  • 我们将添加身份与UserStoreRoleStore
  • 最后,我们将问 ASP.NET Core 返回AuthMessageSender每当我们请求IEmailSenderISMSSender

    ``` public void ConfigureServices(IServiceCollection services { // Add framework services.

            services.AddScoped<ApplicationDbContext>(f => { 
                return new ApplicationDbContext(Configuration["Data:DefaultConnection:ConnectionString"]); 
            });
    
            services.AddIdentity<ApplicationUser, IdentityRole>() 
                .AddUserStore<UserStore<ApplicationUser, ApplicationDbContext>>() 
                .AddRoleStore<RoleStore<ApplicationDbContext>>() 
                .AddDefaultTokenProviders();
    
            services.AddMvc();
    
            // Add application services. 
            services.AddTransient<IEmailSender, AuthMessageSender>(); 
            services.AddTransient<ISmsSender, AuthMessageSender>(); 
        }
    

    ```

创建视图模型

接下来,我们将创建几个 viewmodel,它们将在我们的 Views 模型中使用。

首先,我们将创建一个包含三个属性——EmailPasswordConfirmPasswordRegisterViewModel类。 我们用适当的属性装饰属性,这样我们就可以使用一个不引人注目的 jQuery 验证来使用客户端验证。 我们将所有字段的要求如下:

public class RegisterViewModel 
    { 
        [Required] 
        [EmailAddress] 
        [Display(Name = "Email")] 
        public string Email { get; set; } 

        [Required] 
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 
        [DataType(DataType.Password)] 
        [Display(Name = "Password")] 
        public string Password { get; set; } 

        [DataType(DataType.Password)] 
        [Display(Name = "Confirm password")] 
        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 
        public string ConfirmPassword { get; set; } 
    } 

现在,我们可以创建LoginViewModel model,用户可以使用它登录到您的应用。 还有一个额外的属性RememberMe,勾选该属性后,您无需再次输入密码即可登录:

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

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

        [Display(Name = "Remember me?")] 
        public bool RememberMe { get; set; } 
    } 

创建控制器和关联的操作方法

现在我们需要创建一个AccountController类,在其中我们将定义用于身份验证和授权的操作方法:

public class AccountController : Controller 
    { 
        private readonly UserManager<ApplicationUser> _userManager; 
        private readonly SignInManager<ApplicationUser> _signInManager; 
        private readonly IEmailSender _emailSender; 
        private readonly ISmsSender _smsSender; 
        private readonly ILogger _logger; 

        public AccountController( 
            UserManager<ApplicationUser> userManager, 
            SignInManager<ApplicationUser> signInManager, 
            IEmailSender emailSender, 
            ISmsSender smsSender, 
            ILoggerFactory loggerFactory) 
        { 
            _userManager = userManager; 
            _signInManager = signInManager; 
            _emailSender = emailSender; 
            _smsSender = smsSender; 
            _logger = loggerFactory.CreateLogger<AccountController>(); 
        } 
    } 

在前面的代码中,我们使用由不同组件提供的服务。 UserManagerSignInManager由 ASP 提供。 净的身份。 IEmailSenderISmsSender是我们编写的用于发送电子邮件和短信的自定义类。 我们将在本章后面更多地讨论电子邮件和 SMS。 日志记录是由 Microsoft Logging 扩展提供的。 下面是一个简单的登录HTTPGET方法。 它只是存储从Login方法访问的 URL,并返回登录页面:

[HttpGet] 
        [AllowAnonymous] 
        public IActionResult Login(string returnUrl = null) 
        { 
            ViewData["ReturnUrl"] = returnUrl; 
            return View(); 
        } 

创建视图

现在,我们将为登录创建各自的 View 页面。 在这个视图页面中,我们只显示以下细节:

@using System.Collections.Generic 
@using Microsoft.AspNet.Http 
@using Microsoft.AspNet.Http.Authentication 
@using AspNet.Identity.EntityFramework6 

@model LoginViewModel 
@inject SignInManager<ApplicationUser> SignInManager 

@{ 
    ViewData["Title"] = "Log in"; 
} 

<h2>@ViewData["Title"].</h2> 
<div class="row"> 
    <div class="col-md-8"> 
        <section> 
            <form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form"> 
                <h4>Use a local account to log in.</h4> 
                <hr /> 
                <div asp-validation-summary="ValidationSummary.All" class="text-danger"></div> 
                <div class="form-group"> 
                    <label asp-for="Email" class="col-md-2 control-label"></label> 
                    <div class="col-md-10"> 
                        <input asp-for="Email" class="form-control" /> 
                        <span asp-validation-for="Email" class="text-danger"></span> 
                    </div> 
                </div> 
                <div class="form-group"> 
                    <label asp-for="Password" class="col-md-2 control-label"></label> 
                    <div class="col-md-10"> 
                        <input asp-for="Password" class="form-control" /> 
                        <span asp-validation-for="Password" class="text-danger"></span> 
                    </div> 
                </div> 
                <div class="form-group"> 
                    <div class="col-md-offset-2 col-md-10"> 
                        <div class="checkbox"> 
                            <input asp-for="RememberMe" /> 
                            <label asp-for="RememberMe"></label> 
                        </div> 
                    </div> 
                </div> 
                <div class="form-group"> 
                    <div class="col-md-offset-2 col-md-10"> 
                        <button type="submit" class="btn btn-default">Log in</button> 
                    </div> 
                </div> 
                <p> 
                    <a asp-action="Register">Register as a new user?</a> 
                </p> 
                <p> 
                    <a asp-action="ForgotPassword">Forgot your password?</a> 
                </p> 
            </form> 
        </section> 
    </div> 

</div> 

@section Scripts { 
    @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 
} 

Creating Views

当用户第一次登录到应用时,他们可能没有任何登录凭据,因此我们的应用应该提供一个特性,用户可以使用它为自己创建登录。 我们将创建一个简单的Register操作方法,它将返回一个用户可以注册自己的视图:

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

我们还将创建相应的 View,其中包含电子邮件、密码、密码确认和Register按钮的输入控件:

@model RegisterViewModel 
@{ 
    ViewData["Title"] = "Register"; 
} 

<h2>@ViewData["Title"].</h2> 

<form asp-controller="Account" asp-action="Register" method="post" class="form-horizontal" role="form"> 
    <h4>Create a new account.</h4> 
    <hr /> 
    <div asp-validation-summary="ValidationSummary.All" class="text-danger"></div> 
    <div class="form-group"> 
        <label asp-for="Email" class="col-md-2 control-label"></label> 
        <div class="col-md-10"> 
            <input asp-for="Email" class="form-control" /> 
            <span asp-validation-for="Email" class="text-danger"></span> 
        </div> 
    </div> 
    <div class="form-group"> 
        <label asp-for="Password" class="col-md-2 control-label"></label> 
        <div class="col-md-10"> 
            <input asp-for="Password" class="form-control" /> 
            <span asp-validation-for="Password" class="text-danger"></span> 
        </div> 
    </div> 
    <div class="form-group"> 
        <label asp-for="ConfirmPassword" class="col-md-2 control-label"></label> 
        <div class="col-md-10"> 
            <input asp-for="ConfirmPassword" class="form-control" /> 
            <span asp-validation-for="ConfirmPassword" class="text-danger"></span> 
        </div> 
    </div> 
    <div class="form-group"> 
        <div class="col-md-offset-2 col-md-10"> 
            <button type="submit" class="btn btn-default">Register</button> 
        </div> 
    </div> 
</form> 

@section Scripts { 
    @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } 
} 

以下是对应的POST动作注册方法。 这里,程序检查模型是否有效,如果有效,它将使用模型数据创建一个ApplicationUser对象,并调用 Identity API(CreateAsync方法)。 如果可以创建user变量,则用户将使用该用户 ID 登录,并被重定向到Home页面:

[HttpPost] 
        [AllowAnonymous] 
        [ValidateAntiForgeryToken] 
        public async Task<IActionResult> Register(RegisterViewModel model) 
        { 
            if (ModelState.IsValid) 
            { 
                var user = new ApplicationUser { UserName = model.Email,  Email = model.Email }; 
                var result = await _userManager.CreateAsync(user,  model.Password); 
                if (result.Succeeded) 
                { 
                    await _signInManager.SignInAsync(user, isPersistent:  false); 
                    return RedirectToAction(nameof(HomeController.Index),  "Home"); 
                } 
                AddErrors(result); 
            } 

            return View(model); 
        } 

登出功能非常简单。 它只需要调用 Identity API 的SignoutAsync方法,并被重定向到Index页面:

[HttpPost] 
        [ValidateAntiForgeryToken] 
        public async Task<IActionResult> LogOff() 
        { 
            await _signInManager.SignOutAsync(); 
            _logger.LogInformation(4, "User logged out."); 
            return RedirectToAction(nameof(HomeController.Index), "Home"); 
       } 

回到登录功能,下面是各自的操作方法。 我们正在调用 Identity API 的PasswordSignInAsync方法。 成功登录后,我们将访问登录功能的 URL 重定向:

[HttpPost] 
        [AllowAnonymous] 
        [ValidateAntiForgeryToken] 
        public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null) 
        { 
            ViewData["ReturnUrl"] = returnUrl; 
            if (ModelState.IsValid) 
            { 
                var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); 
                if (result.Succeeded) 
                { 
                    return RedirectToLocal(returnUrl); 
                } 

            } 
            // If there is any error, display the form again 
            return View(model); 
        } 

电子邮件和短信服务

如果你想将电子邮件和短信服务添加到你的应用的身份验证功能中,你可以通过创建如下所示的接口和类来实现:

public interface IEmailSender
{
        Task SendEmailAsync(string email, string subject, string message)
  }
  public interface ISmsSender
    {
        Task SendSmsAsync(string number, string message);
    }
public class AuthMessageSender : IEmailSender, ISmsSender
    {
        public Task SendEmailAsync(string email, string subject, string message)
        {
            // We can plug in our email service here to send an email.
            return Task.FromResult(0);
        }
        public Task SendSmsAsync(string number, string message)
        {
            // We can plug in our SMS service here to send a text message.
            return Task.FromResult(0);
        }
    }

在控制器中固定操作方法

为了便于解释,让我们假设About页面是一个安全页面,只有经过身份验证的用户才能访问它。

我们只需要用[Authorize]属性装饰Home控制器中的About动作方法:

[Authorize] 
        public IActionResult About() 
        { 
            ViewData["Message"] = "This is my about page"; 
            return View(); 
        } 

当用户试图访问登录页面而不登录应用时,进行上述更改将把用户重定向到登录页面:

Securing an  method in a Controller

在下面的屏幕截图中,您将注意到 URL 中有一个额外的查询参数ReturnURL,。 这个ReturnURL参数将应用重定向到该特定的页面(在本例中,通过ReturnURL参数—Home/About传递的值)。

一旦您登录,您将被重定向到您先前请求的页面:

Securing an  method in a Controller

当您注册一个新用户时,用户的详细信息将存储在由 ASP 创建的相关表中。 净的身份。

打开 SQL Server 对象管理器窗口,选择View|SQL Server 对象管理器,如下图所示:

Securing an  method in a Controller

一旦您选择SQL Server 对象管理器选项,您将看到一个类似如下截图的窗口。 ASP.NET Identity 使用实体框架和前面在appsettings.json包中提供的连接字符串为我们创建数据库。

ASP.NET Identity 创建几个表来维护与身份相关的信息和实体框架的数据库迁移历史。 因为我们使用 ASP.NET 身份在基本级别上,除了dbo 之外,没有任何与身份相关的表会被填充。 AspNetUsers.:

Securing an  method in a Controller

您可以右键单击dbo。 AspNetUsers表,选择View Data查看数据:

Securing an  method in a Controller

由于在我们的应用中只注册了一个用户,所以只创建了一行。 请注意,散列密码(由 ASP 标记.NET Identity 为我们),并且表中不会存储空白密码。

总结

在本章中,我们学习了身份验证和授权。 我们还学习了如何实现 ASP。 asp.net 标识.NET Core 应用,遵循一个循序渐进的过程。 我们还讨论了 ASP 中涉及到的表.NET 身份,并学习了如何查看由 asp.net 创建的数据。 净的身份。