十七、安全

在本章中,我们将涵盖以下主题:

  • 验证在 ASP.NET 使用 cookie 认证
  • 使用授权服务器进行身份验证
  • 身份管理
  • 用 ASP 保护数据.NET Core
  • 哈希
  • 加密

介绍

在本节中,我们将看看在 ASP 中身份验证是如何工作的.NET 的核心。

All examples in this chapter can be found at https://github.com/polatengin/B05277/tree/master/Chapter17 GitHub repo.

HTTP 协议是一种无状态的响应请求协议。 这意味着 HTTP 服务器在收到请求后可以生成响应,而且它不会记住以前的请求及其结果。 每个请求都是单独处理的。

例如,如果应用要求您首先登录,开发人员应该处理所需的逻辑流,以便在用户还没有登录时重定向到用户登录页面。

因此,每个请求都应该拥有要成功处理的所有信息(如果用户已登录或未登录,用户是谁,以及他们的权限)。

如果一个糟糕的用户位于客户机和服务器之间,他们可以阅读包,并很容易地假装是其他人。

大多数服务器端框架(ASP.NET、Java、Ruby 等)提供了一些机制,为开发人员提供了从服务器内存存储和访问这类信息的解决方案。

ASP.NET 也不例外,它有 api 来存储/访问/处理服务器内存中的用户信息。

Authentication is the process of determining whether someone or something is, in fact, who or they say they are ,what it says it is: http://searchsecurity.techtarget.com/definition/authentication. Authorization is the process of giving someone permission to do or have something: http://searchsoftwarequality.techtarget.com/definition/authorization.

真实世界中的身份验证

想象一下,你把家里的前门锁上,然后把钥匙藏在门垫下面。 找到并使用钥匙打开前门的人就可以进入整个房子。

简单的锁没有任何机制来确定钥匙持有者是房主还是小偷。

如果你用虹膜扫描仪代替一个简单的锁,你可以同时授权和认证用户。

虹膜扫描仪首先确定用户(身份验证),然后确定允许或拒绝用户访问家庭的权限(授权)。

验证样本

让我们创建一个新的 ASP.NET Core 2.0 web 项目,并查看流程:

An example project can be found at: https://github.com/polatengin/B05277/tree/master/Chapter17/0-AuthenticationSample.

  1. 首先,打开终端/命令提示符,并导航到我们想要创建项目的文件夹。 你可以在 GitHub(https://github.com/polatengin/B05277)上找到以下项目:
    dotnet new web -n AuthenticationSample
    dotnet restore
  1. 现在我们可以在 Visual Studio Code 中打开AuthenticationSample文件夹,用下面的代码替换app.Run()方法体:
var list = new[] { 
    new { Id=1, Title="Istanbul" }, 
    new { Id=2, Title="New York" }, 
    new { Id=3, Title="Madrid" }, 
    new { Id=4, Title="Rome" }, 
    new { Id=5, Title="Vienna" }, 
}; 
var json = JsonConvert.SerializeObject(list); 

context.Response.ContentType = "text/json"; 
await context.Response.WriteAsync(json); 
  1. 我们可以在终端/命令提示符中运行以下命令来编译和运行项目:
dotnet run 
  1. 现在我们可以在浏览器中导航到http://localhost:5000,查看列表变量的 JSON 输出:

Up to this point, everything went well. But other users can call this endpoint. What if I want to bar unauthorized users?

验证在 ASP.NET,使用 cookie 身份验证

cookie 认证机制发送一个安全的带有授权信息的 cookie。 客户端向服务器发出的每个请求都包含该安全 cookie,服务器端应用可以从安全 cookie 中识别用户。

An example project can be found at: https://github.com/polatengin/B05277/tree/master/Chapter17/1-CookieAuthenticationSample.

准备

要完成这个示例,您需要创建一个简单的 ASP.NET Core 2.0 项目,并在其中添加一些库。

怎么做……

我们将创建一个新的 ASP.NET Core Web 项目,并添加所需的库来使用 cookie 在其中保存经过身份验证的用户信息:

  1. 让我们创建一个新的 ASP.NET Core web 项目,并使用 cookie 认证配置它:
dotnet new web -n CookieAuthenticationSample 
dotnet add package Microsoft.AspNetCore.Authentication.Cookies 
dotnet restore 
  1. 现在我们需要在Startup.cs文件中在Configure()方法的app.UseMvc()app.UseMvcWithDefaultRoute()行之前添加以下行:
 app.UseAuthentication(); 
  1. 我们还需要在ConfigureServices()方法中添加以下一行,在services.AddMvc()行之前,如下:
 services.AddAuthentication("CookieAuthenticationScheme") 
        .AddCookie("CookieAuthenticationScheme", options => { 
            options.LoginPath = "/Home/Login"; 
        }); 

LoginPath属性是用户尚未登录的重定向点。

  1. 如果你需要返回一个 401 HTTP 状态码而不是重定向,这可以很容易地通过替换之前的代码与以下:
services.AddAuthentication("CookieAuthenticationScheme") 
        .AddCookie("CookieAuthenticationScheme", options => { 
            options.Events.OnRedirectToLogin = (context) => { 
                context.Response.StatusCode = 401; 
               return Task.CompletedTask; 
            }; 
        }); 
  1. 我们需要一个数据模型来保存用户凭证; 让我们创建一个文件夹,命名为Models,并添加一个LoginModel.cs文件:
public class LoginModel 
{ 
   public string Email { get; set; } 
   public string Password { get; set; } 
} 
  1. 现在我们在Controllers文件夹中创建一个HomeController.cs文件,并向其添加Login()操作:
[HttpGet] 
public IActionResult Login() 
{ 
   return View(); 
} 

[HttpPost] 
public async Task<IActionResult> Login(LoginModel model) 
{ 
   if(LoginUser(model.Email, model.Password)) 
   { 
      var claims = new List<Claim> 
      { 
         new Claim(ClaimTypes.Email, model.Email) 
      }; 

      var identity = new ClaimsIdentity(claims, "login"); 

      var principal = new ClaimsPrincipal(identity); 
      await HttpContext.SignInAsync("CookieAuthenticationScheme", principal); 

      //Redirect user to home page after login. 
      return RedirectToAction(nameof(Index)); 
   } 

   return View(); 
} 
  1. 让我们添加Login()动作,以显示Views/Home文件夹中的Login.cshtml文件,其中包含以下 HTML:
<form method="post"> 
    <input type="email" name="email" /> 
    <input type="password" name="password" /> 
    <input type="submit" value="Login" /> 
</form> 
  1. 其次,Login()方法接收电子邮件和密码字段值,并将它们传递给以下LoginUser()方法:
private bool LoginUser(string email, string password) 
{ 
   //TODO: add DB logic here 
   return true; 
} 

The LoginUser() method is just a placeholder; you should replace it with database backend logic.

  1. 如果LoginUser()方法返回 true,Login()操作将创建一个新的Claim并使用该Claim创建一个新的ClaimIdentity

HttpContext.SignInAsync()方法(来自我们在开始安装的Microsoft.AspNetCore.Authentication.CookiesNuGet 包)接收一个模式名和ClaimsPrincipal

  1. 我们从前面创建的ClaimsIdentity实例化了一个新的ClaimsPrincipal

It's important that we should provide the correct schema names, which we stated in the AddAuthentication() call in the ConfigureServices() method in the Startup.cs file.

  1. 在使用HttpContext.SignInAsync()方法之后,我们可以将用户重定向到一个安全页面。 HttpContext.SignOutAsync()方法之前的每个请求都可以用来保护页面。

  2. HttpContext.SignOutAsync()方法使用提供的模式名清除AuthenticationCookie,然后,对安全页面的请求将失败并重定向到登录页面。

  3. 现在我们准备将一些页面标记为安全的。 这是一个相对简单的任务; 我们只需要用[Authorize]类来注释动作或控制器:
[Authorize] 
public class MailboxController : Controller 
{ 
    public IActionResult Index() 
    { 
        return View(); 
    } 
} 

它是如何工作的…

在身份验证之前,/mailbox/index页面会重定向到登录页面。 身份验证之后,/mailbox/index页面将工作。

基本上,应用拒绝认证前的每个请求,并将它们重定向到登录页面。

身份验证后,浏览器会接收一个身份验证 cookie,并接受需要身份验证的请求。

使用授权服务器进行身份验证

如果有人试图访问你的 web 应用的安全部分,但他们还没有登录,应用会将用户重定向到授权服务器,以识别他们自己。 通常,这意味着用户输入他们的凭证(用户名、电子邮件、密码等等)。

授权服务器只需完成一项工作,即使用用户的凭据对其进行身份验证并返回索赔。

索赔基本上是授予特权列表。 一个用户可以有多个声明来使用应用的某些部分,比如查看账单历史记录、添加账单、从历史记录中删除账单都是不同的声明。

一旦授权服务器用提供的凭证验证了用户,它就生成一个映射到用户的令牌,它要么向用户发出该令牌,要么将用户重定向到应用。

如果用户试图访问 web 应用的安全部分,它将检查令牌,并在令牌有效的情况下授权用户。

一些授权服务器:

准备

要使用授权服务器,我们需要创建一个项目,添加 NuGet 包,并在授权服务器提供者中创建帐户。

有些授权提供程序需要您创建开发人员帐户。

怎么做……

让我们创建一个新的 ASP.NET Core web 项目和添加 Facebook 认证和谷歌认证 NuGet 包; 为此,运行以下命令:

An example project can be found at: https://github.com/polatengin/B05277/tree/master/Chapter17/2-FacebookGoogleAuthenticationSample.

dotnet new web -n FacebookGoogleAuthenticationSample 

dotnet add package Microsoft.AspNetCore.Authentication.Facebook 

dotnet add package Microsoft.AspNetCore.Authentication.Google 

dotnet restore 

使用 Facebook 作为授权服务器

现在我们需要在 Facebook 开发者门户网站(http://developers.facebook.com)注册我们的项目为 Facebook 应用:

  1. 首先,我们应该在 Facebook 开发者门户中输入 Facebook 凭据。 使用 Add a new app 按钮创建一个应用,并填写以下表格:

  1. 在创建一个应用之后,我们应该看到仪表板。 在左侧,有一个+添加产品菜单; 当点击它时,它将显示推荐产品页面。
  2. Facebook 登录面板就是我们要找的。 它负责为特定的 Facebook 应用添加一个功能,以便使用 Facebook 凭据登录。

  3. 我们只需要点击 Setup 按钮并调整一些设置; 就是这么简单:

  1. 在打开的页面中,只需填写 Valid OAuth 并使用http://localhost:5000/signin-facebookURL 重定向 uri 文本框。 这个 URL 将是我们项目的端点,它处理 Facebook Login 的返回值。

We should change this URL to our production endpoint before publishing our app in a production environment.

我们还没有改变其他复选框或文本框。 但是,在将项目发布到生产环境之前,我们应该重新访问前面的页面。

  1. 保存 Facebook 登录页面设置后,我们应该回到 Facebook 应用的 Dashboard 页面,找到应用 ID 和应用秘密信息:

  1. 点击 Show 按钮查看 App Secret。
  2. 现在回到项目。 让我们打开Startup.cs文件,在ConfigureServices()方法中添加一些代码:
public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=FacebookGoogleAuthenticationSample;Data Source=.")); 

    services.AddIdentity<ApplicationUser, IdentityRole>() 
            .AddEntityFrameworkStores<ApplicationDbContext>() 
            .AddDefaultTokenProviders(); 

    services.AddAuthentication( 
            CookieAuthenticationDefaults.AuthenticationScheme) 
            .AddCookie(o => o.LoginPath = new PathString("/home/login")) 
            .AddFacebook(facebook => 
            { 
                facebook.AppId = "208906605857885"; 
                facebook.AppSecret = "90e620f09fbadfaa01f74e2bc3"; 
            }); 

    services.AddMvc(); 
} 
  1. 你应该将 Facebook 应用 ID 和 Facebook 应用秘密更改为你自己的应用 ID,并将 App 秘密更改为你自己的。

下面是之前提供的代码块中的几个重要指针:

Identity is enabled for the application by calling UseAuthentication in the Configure method and AddIdentity and AddAuthentication in the ConfigureServices method.

  1. 在同一个文件(Startup.cs)中,我们还应该更改Configure()方法:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    if (env.IsDevelopment()) 
    { 
        app.UseDeveloperExceptionPage(); 
    } 

    app.UseAuthentication(); 

    app.UseMvc((routes) => 
    { 
        routes.MapRoute("SigninFacebook", "signin-facebook", new { controller = "Home", action = "FacebookOK" }); 
        routes.MapRoute("Default", "{controller=Home}/{action=Index}"); 
    }); 
} 

现在我们有了一个强大的基线,我们可以继续以下内容:

  1. 让我们创建ModelsViews,和Controllers文件夹,并在Views文件夹中创建一个Home文件夹:
  2. 我们需要在Models文件夹中创建一个ApplicationUser.cs文件:
using Microsoft.AspNetCore.Identity; 

public class ApplicationUser : IdentityUser 
{ 
} 
  1. 我们没有添加自定义字段来存储在数据库中。 在Models文件夹中也创建ApplicationDbContext.cs文件:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 
using Microsoft.EntityFrameworkCore; 

public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 
{ 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) 
    { 
    } 
} 
  1. 让我们在Controllers文件夹中创建一个HomeController.cs文件,并为其添加一个构造函数:
private readonly SignInManager<ApplicationUser> _signInManager; 

public HomeController(SignInManager<ApplicationUser> signInManager) => this._signInManager = signInManager; 
  1. 使用_signinManager变量,我们可以让用户从 Facebook 登录和退出。 首先,从Index()操作返回索引视图:
public IActionResult Index() => View(); 
  1. 我们需要在Views/Home文件夹中有一个Index.cshtml文件,这是一个相对简单的任务:
<a href="/home/facebook">Facebook Login</a> 
  1. HomeController.cs文件中,添加Facebook()动作,如下所示:
public IActionResult Facebook() 
{ 
  var properties = 
  _signInManager.ConfigureExternalAuthenticationProperties
  ("Facebook", "/Home/FacebookOK"); 
  return Challenge(properties, "Facebook"); 
} 
  1. 我们使用_signInManager变量来处理 FacebookAuthentication进程。 在 Facebook 登录后,流将继续与FacebookOK()动作:
public async Task<IActionResult> FacebookOK() 
{ 
  var info = await 
  _signInManager.GetExternalLoginInfoAsync(); 
  //info.Principal 
  //structure that holds Claims, provided by Facebook 
  //it includes, Facebook Unique Identifier, Facebook 
  Email, Facebook Name, etc. 

  //info.ProviderKey 
  //Facebook Unique Identifier for logged in user 

  return RedirectToAction(nameof(Index)); 
}  
  1. 我们使用_signInManager变量获取额外的信息,这些信息将由 FacebookAuthentication流程提供,然后将用户重定向到Index()动作/页面。

  2. 在此重定向之后,用户可以自由地在应用中导航,特别是带有[Authorize]属性的动作/控制器,以前无法导航:

using Microsoft.AspNetCore.Authorization; 
using Microsoft.AspNetCore.Mvc; 

[Authorize] 
public class MailboxController : Controller 
{ 
    public IActionResult Index() => View(); 
} 

现在,我们有向匿名用户开放的页面,以及由 Facebook Login 机制保护的页面,该机制只对登录的用户开放。

使用谷歌作为授权服务器

现在是谷歌认证时间:

  1. 让我们导航到谷歌开发人员控制台(https://console.developers.google.com),并使用谷歌凭证登录:

  1. 登录到谷歌开发人员控制台后,我们需要通过单击 create 按钮并填写以下表单来创建一个项目:

  1. 当我们在谷歌开发人员控制台中创建项目时,它会立即将我们重定向到项目仪表板。
  2. 我们应该点击启用 API 和服务按钮,然后从列表中选择谷歌+ API:

  1. 使用谷歌+ API,我们可以为我们的项目启用谷歌身份验证。 只需单击启用按钮。
  2. 当谷歌为我们的项目启用谷歌+ API 时,它将导航到谷歌+ API 仪表板。
  3. 我们应该点击 Create Credentials 按钮并填写以下表单:

  1. 当我们点击“What credentials do I need?” 按钮,谷歌开发人员控制台询问以下问题:

We should change this URL to our production endpoint before publishing our app in a production environment.

表格上有三个重要的信息:

  1. 现在我们可以点击 Create client ID 按钮; 之后,我们可以复制客户端 ID 并下载客户端证书文件:

客户端凭据文件为 JSON 格式文件(client_id.json),包含client_idproject_idclient_secret信息。 我们需要这些信息

.AddGoogle(google => 
{ 
    google.ClientId = "1074523660996-lmhrgsuhau31ptca5ajln1334t94b4tj.apps.googleusercontent.com"; 
    google.ClientSecret = "fW3fnRzs7DQa9WEUVbaMhCAL"; 
}) 

It is important to change Google Client Id and Google Client Secret values before publishing to production values.

  1. 我们需要将以下一行添加到Configure()方法中,就在SigninFacebook行之前:
routes.MapRoute("SigninGoogle", "signin-google", new { controller = "Home", action = "GoogleOK" }); 
  1. 打开Views/Home文件夹下的Index.cshtml,修改内容如下:
<a href="/home/facebook">Facebook Login</a> 
<br /> 
<a href="/home/google">Google Login</a> 

通过这样做,用户可以登录 Facebook 或谷歌。

  1. 现在打开HomeController.cs文件,添加Google()动作:
public IActionResult Google() 
{ 
    var properties = _signInManager.ConfigureExternalAuthenticationProperties("Google", "/Home/GoogleOK"); 

    return Challenge(properties, "Google"); 
} 
  1. 我们只需要HomeController.cs文件中的GoogleOK()方法,如下所示:
public async Task<IActionResult> GoogleOK() 
{ 
  var info = await 
  _signInManager.GetExternalLoginInfoAsync(); 
  //info.Principal 
  //structure that holds Claims, provided by Google 
  //it includes, Google Unique Identifier, Google Email, 
  Google Name, etc. 
  //info.ProviderKey 
  //Google Unique Identifier for logged in user 
  return RedirectToAction(nameof(Index)); 
} 

它是如何工作的…

未经授权的请求将以RedirectToAction()方法响应,并将其重定向到 Facebook 或谷歌的登录方法。

当用户向 Facebook 或谷歌授权服务器(登录页面)中的应用授予权限时,它会向我们的应用携带一个令牌,我们坚持它标识一个个人。

身份管理

现在,我们准备运行我们的应用,并让用户通过 Facebook 登录或谷歌登录机制登录。

Facebook 和谷歌都返回ProviderKey属性来识别单个用户。 我们可以在数据库中保存该信息,并将其与另一个表(如 ShoppingCart、PurchaseHistory 等)关联起来。 这样,我们可以让 Facebook 和谷歌管理用户,但我们仍然可以在数据库系统中引用该用户。

ASP 的美.NET Core 身份是我们可以轻松地将另一个系统插入其中(如 Linkedin、Microsoft Live、GitHub、Auth0 等)。 它只是在授权服务器系统中添加一个库并创建一个应用。 在 ASP.NET Core,我们可以创建一个空项目,并将其转化为一个具有身份识别功能的完整应用。

微软发布了Microsoft.AspNetCore.IdentityNuGet 包(https://www.nuget.org/packages/Microsoft.AspNetCore.Identity/),帮助开发者轻松创建认证和授权机制。

这个包也是Microsoft.AspNetCore.AllNuGet 包(https://www.nuget.org/packages/Microsoft.AspNetCore.All/)的一部分。

新的 ASP.NET Core web 项目已经包含了Microsoft.AspNetCore.AllNuGet 包,我们不需要在我们的项目中确认身份。

An example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter17/3-AspNetCoreIdentitySample.

准备

在 ASP 中管理身份没有什么特别需要准备的.NET 的核心。 只需创建一个空项目和一些 NuGet 包; 就是这样。

It's assumed that the app can reach a SQL Server, with the required permissions.

怎么做……

让我们深入研究代码:

  1. 首先,在命令提示符/终端中运行以下脚本创建一个新项目:
dotnet new web -n AspNetCoreIdentitySample 
dotnet restore 
  1. 打开Startup.cs文件,修改ConfigureServices()方法,如下:
public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer("Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=;Data Source=.")); 

    services.AddIdentity<ApplicationUser, IdentityRole>() 
            .AddEntityFrameworkStores<ApplicationDbContext>() 
            .AddDefaultTokenProviders(); 

    services.AddMvc(); 
} 
  1. 我们设置连接字符串,并使用数据库名称AspNetCoreIdentitySample将其指向本地数据库服务器。

It's not a problem if we have no database with that name, EntityFrameworkCore will create it before using it the first time.

  1. 此外,我们声明,我们想要在ApplicationUser类和ApplicationDbContext数据库连接层中使用 Identity。
  2. 现在我们需要更改Configure()方法,如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    if (env.IsDevelopment()) 
    { 
        app.UseDeveloperExceptionPage(); 
    } 

    app.UseAuthentication(); 

    app.UseMvcWithDefaultRoute(); 
} 
  1. 我们创建一个Models文件夹,并添加ApplicationUser.cs文件,如下所示:
using System; 
using Microsoft.AspNetCore.Identity; 

public class ApplicationUser : IdentityUser 
{ 
    public string CityName { get; set; } 

    public DateTime JobBeginDate { get; set; } 
} 
  1. 我们刚刚创建了一个新文件来开发ApplicationUser类。 我们应该注意到,ApplicationUser类应该继承自IdentityUser类,而IdentityUser类来自Microsoft.AspNetCore.Identity名称空间。 IdentityUser类具有以下属性:
public virtual DateTimeOffset? LockoutEnd { get; set; } 

public virtual bool TwoFactorEnabled { get; set; } 

public virtual bool PhoneNumberConfirmed { get; set; } 

public virtual string ConcurrencyStamp { get; set; } 
public virtual string SecurityStamp { get; set; } 

public virtual string PasswordHash { get; set; } 

public virtual bool EmailConfirmed { get; set; } 

public virtual string NormalizedEmail { get; set; } 

public virtual string Email { get; set; } 

public virtual string NormalizedUserName { get; set; } 

public virtual string UserName { get; set; } 

public virtual TKey Id { get; set; } 

public virtual bool LockoutEnabled { get; set; } 

public virtual int AccessFailedCount { get; set; } 
  1. 如果我们需要关于用户的额外信息,我们可以添加一个属性派生类(在本例中是ApplicationUser类)。
  2. 我们添加了CityNameJobBeginDate属性。
  3. 现在我们需要在Models文件夹中创建一个ApplicationDbContext.cs文件:
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 
using Microsoft.EntityFrameworkCore; 

public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 
{ 
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) 
    { 
    } 
} 

EntityFrameworkCore自动在目标服务器中创建一个数据库,该数据库中的表,等等。

  1. Controllers文件夹中创建HomeController.cs文件,添加两个类级变量和构造函数,如下所示:
private readonly UserManager<ApplicationUser> userManager; 
private readonly SignInManager<ApplicationUser> loginManager; 

public HomeController(ApplicationDbContext dc, UserManager<ApplicationUser> userManager, <ApplicationUser> loginManager) 
    { 
        this.userManager = userManager; 
        this.loginManager = loginManager; 

        dc.Database.EnsureCreated(); 
    } 
  1. UserManagerSignInManager类管理用户的创建、登录和退出系统。 让我们创建一些动作:
public IActionResult Index() => View(); 

[HttpGet] 
public IActionResult Register() => View(); 

[HttpGet] 
public IActionResult Login() => View(); 
  1. 现在我们需要一个包含Index.cshtmlRegister.cshtmlLogin.cshtml文件的Views/Home文件夹:
<a href="/home/login">Login</a>
<a href="/home/register">Register</a>
<a href="/home/logout">Logout</a>
<h1>Register</h1> 
<form action="/home/register" method="post"> 
    <table> 
        <tr> 
            <td>UserName</td> 
            <td><input type="text" name="username" /></td> 
        </tr> 
        <tr> 
            <td>Password</td> 
            <td><input type="password" name="password" /></td> 
        </tr> 
        <tr> 
            <td>Confirm Password</td> 
            <td><input type="password" name="confirmPassword" /></td> 
        </tr> 
        <tr> 
            <td>Email</td> 
            <td><input type="email" name="email" /></td> 
        </tr> 
        <tr> 
            <td>Phone Number</td> 
            <td><input type="tel" name="phoneNumber" /></td> 
        </tr> 
        <tr> 
            <td>City Name</td> 
            <td><input type="text" name="cityName" /></td> 
        </tr> 
        <tr> 
            <td>Job Begin Date</td> 
            <td><input type="date" name="jobBeginDate" /></td> 
        </tr> 
        <tr> 
            <td><input type="submit" value="Register" /></td> 
        </tr> 
    </table> 
</form> 

我们从客户端获得了足够的数据来创建用户。

Notice, we're getting Email, PhoneNumber, CityName, JobBeginDate

<h1>Login</h1> 
<form action="/home/login" method="post"> 
    <table> 
        <tr> 
            <td>UserName</td> 
            <td><input type="text" name="username" /></td> 
        </tr> 
        <tr> 
            <td>Password</td> 
            <td><input type="password" name="password" /></td> 
        </tr> 
        <tr> 
            <td>Remember Me</td> 
            <td><input type="checkbox" name="rememberme" /></td> 
        </tr> 
        <tr> 
            <td><input type="submit" value="Login" /></td> 
        </tr> 
    </table> 
</form> 
  1. 让我们回到HomeController.cs,添加一个Login()动作,如下所示:
[HttpPost] 
public async Task<IActionResult> Login(string username, string password, string rememberme) 
{ 
  var result = await 
  this.loginManager.PasswordSignInAsync(username, 
  password, rememberme == "on", false); 

  return RedirectToAction(nameof(Index)); 
} 

我们使用loginManager变量通过调用PasswordSignInAsync()方法来登录用户。 如果我们将false作为第三个参数传递,那么用户每次打开新浏览器时都必须登录。 如果我们传递true作为第三个参数,ASP.NET Core Identity 生成一个 cookie,将其传递给客户端,并在他们打开新浏览器时记住它们:

  1. 注销是一项相对容易的任务; 使用以下代码:
public IActionResult Logout() 
{ 
    this.loginManager.SignOutAsync(); 

    return RedirectToAction(nameof(Index)); 
} 

我们只是使用了loginManager变量并调用了SignOutAsync()方法来成功地退出应用。

The PasswordSignInAsync() method creates a cookie if the third parameter is true. The SignOutAsync() method deletes it, if it exists.

  1. 最后一个方法是Register方法:
[HttpPost] 
public async Task<IActionResult> Register(string username, string email, string password, string phoneNumber, string cityName, DateTime jobBeginDate) 
{ 
    var user = new ApplicationUser() 
    { 
        UserName = username, 
        Email = email, 
        PhoneNumber = phoneNumber, 
        CityName = cityName, 
        JobBeginDate = jobBeginDate 
    }; 

    var result = await this.userManager.CreateAsync(user, password); 

    //TODO: check for 
    //result.Succeeded and result.Errors 

    return RedirectToAction(nameof(Index)); 
} 

Register()方法使用userManager变量创建用户。 如果我们提供带有第二个参数的密码,则创建的用户将拥有该密码。 否则,创建的用户没有密码。

在前面的代码示例中,我们有结果变量。 Result变量要么填充Succeeded属性,要么填充Errors属性。 我们应该迭代Errors属性以列出错误,例如密码太短、复杂性不够、没有提供用户名或电子邮件,等等。

如您所见,数据库和所有必需的表都在第一次自动创建。

  1. 我们可以点击注册链接,填写表格,如下:

数据库中的AspNetUsers表将有一个新行:

  1. 现在我们可以登录并使用整个应用:

它是如何工作的…

将应用部署到服务器并运行它。 用户可以创建一个帐户并使用它登录。 所有数据都持久化在 SQL Server 中。

ASP.NET Core Identity 使得使用 SQL Server 数据库向我们的 web 应用添加身份验证和授权机制变得非常容易。

ASP.NET Core Identity 库可以使用 NuGet 包管理器添加到项目中。

基本上,ASP.NET Core Identity 库添加了不同的类来管理应用的用户及其权限。 它还包括一种机制,用于在数据库中生成表来保存用户和相关信息。

用哈希法保护数据

.NET Core 现在在以下操作系统中使用加密 API:

  • macOS 上的苹果安全框架
  • OpenSSL Linux 上
  • 密码 API:下一代(CNG

有两种保护数据的机制:散列和加密。

哈希是一种单向机制,无法将经过哈希处理的数据返回到原始状态。

另一方面,加密是一种双向机制,您可以通过解密将加密的数据返回到原始状态。

有很多算法你可以使用 ASP.NET Core,如 Hash、SHA256、SHA512、AES、RSA、MD5 等。 更多信息,请访问:https://www.nuget.org/packages/System.Security.Cryptography.Algorithms/

An example project can be found at: https://github.com/polatengin/B05277/tree/master/Chapter17/4-HashingData.

准备

在 ASP 中没有什么特别的需要准备的.NET 的核心。 只需创建一个空项目和一些 NuGet 包; 就是这样。

怎么做……

在这个例子中,我们将使用 SHA512:

  1. 让我们新建一个项目,如下所示:
dotnet new console -n HashingData 
dotnet restore 
  1. 打开Program.cs,修改如下:
static void Main(string[] args) 
{ 
    var hashed = CalculateHash("Hello World!"); 
    Console.WriteLine(hashed); 

    Console.ReadKey(); 
} 

static string CalculateHash(string input) 
{ 
    using (var algorithm = SHA512.Create()) 
    { 
        var b = algorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); 

        return BitConverter.ToString(b).Replace("-", "").ToLower(); 
    } 
} 
  1. 我们可以运行这个应用并看到散列输出:

There is no way to unhash or restore original data from a hashed version.

它是如何工作的…

我们可以使用CalculateHash()方法获取所提供文本的散列值。 这是一个单向的过程,所以我们无法在散列之后获得提供的文本。

ASP.NET Core 有内置的类来为不同的算法散列给定的数据。 散列是一种单向操作,给定的数据在被散列之后不能被检索。

使用加密技术保护数据

ASP 中也有加密算法.NET Core,如 AES、Des、3Des (TripleDes)、Rijndael、RC2 等。 更多信息,请访问https://docs.microsoft.com/en-us/dotnet/standard/security/cryptography-model

An example project can be found at https://github.com/polatengin/B05277/tree/master/Chapter17/5-EncryptDecryptData.

准备

没有什么特别的准备在 ASP 加密.NET 的核心。 只需创建一个空项目和一些 NuGet 包; 就是这样。

怎么做……

  1. 让我们创建一个新项目,深入研究代码,以便更好地理解 ASP 中的加密和解密.NET Core:
dotnet new console -n EncryptDecryptData 
dotnet restore 
  1. 现在打开Program.cs文件,添加Encrypt()Decrypt()方法,如下所示:
static string Encrypt(string text, string key) 
{ 
    var _key = Encoding.UTF8.GetBytes(key); 

    using (var aes = AES.Create()) 
    { 
        using (var encryptor = aes.CreateEncryptor(_key, aes.IV)) 
        { 
            using (var ms = new MemoryStream()) 
            { 
                using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) 
                { 
                    using (var sw = new StreamWriter(cs)) 
                    { 
                        sw.Write(text); 
                    } 
                } 

                var iv = aes.IV; 

                var encrypted = ms.ToArray(); 

                var result = new byte[iv.Length + encrypted.Length]; 

                Buffer.BlockCopy(iv, 0, result, 0, iv.Length); 
                Buffer.BlockCopy(encrypted, 0, result, iv.Length, encrypted.Length); 

                return Convert.ToBase64String(result); 
            } 
        } 
    } 
} 

static string Decrypt(string encrypted, string key) 
{ 
    var b = Convert.FromBase64String(encrypted); 

    var iv = new byte[16]; 
    var cipher = new byte[16]; 

    Buffer.BlockCopy(b, 0, iv, 0, iv.Length); 
    Buffer.BlockCopy(b, iv.Length, cipher, 0, iv.Length); 

    var _key = Encoding.UTF8.GetBytes(key); 

    using (var aes = AES.Create()) 
    { 
        using (var decryptor = aes.CreateDecryptor(_key, iv)) 
        { 
            var result = string.Empty; 
            using (var ms = new MemoryStream(cipher)) 
            { 
                using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) 
                { 
                    using (var sr = new StreamReader(cs)) 
                    { 
                        result = sr.ReadToEnd(); 
                    } 
                } 
            } 

            return result; 
        } 
    } 
} 

在本例中,我们使用 AES 加密器。 Ecnrypt()Decrypt()方法通过方法参数获得加密密钥,对提供的字符串进行加密或解密,并返回加密/解密版本。

  1. 我们只需要更改Main()方法,如下所示:
static void Main(string[] args) 
{ 
    var key = Guid.NewGuid().ToString("N"); 

    var original = "Hello World!"; 
    var encrypted = Encrypt(original, key); 
    var decrypted = Decrypt(encrypted, key); 

    Console.WriteLine(original); 
    Console.WriteLine(encrypted); 
    Console.WriteLine(decrypted); 

    Console.ReadKey(); 
} 
  1. 当我们运行应用时,我们可以看到原始的、加密的和解密的值如下:

它是如何工作的…

我们可以使用Encrypt()方法获取所提供文本的加密值。 这是一个双向过程,因此我们可以得到加密文本的解密值。

ASP.NET Core 也有内置的类,可以用不同的算法加密和解密给定的数据。 加密是一种双向操作,所提供的数据经过与加密算法相同的解密操作加密后,即可进行计算。