Skip to content

Files

Latest commit

18564be · Jan 9, 2022

History

History
367 lines (256 loc) · 15.8 KB

10.md

File metadata and controls

367 lines (256 loc) · 15.8 KB

十、认证

如果你正在使用一个网络框架来构建供公众使用的网络服务,那么你可能需要某种方式来控制谁可以访问所述服务中的哪些设施。

这可能就像确保应用编程接口用户在访问您的应用编程接口之前在标题中提供一个密钥一样简单,也可能是您正在构建一个必须支持不同访问级别的多个用户的整个平台。

同样,Nancy 已经涵盖了这里的所有基础,实现身份验证方案非常容易。和许多其他特性一样,所有东西都是模块化的,可以使用 NuGet 安装,在身份验证的情况下,默认情况下什么都不安装。

可用的认证方法

在我给你举一个例子之前,我将回顾一下可以从 NuGet 安装的三种方法。你可以通过搜索“Nancy”来找到它们。验证”并安装所需的包。

虽然安装多种方法非常好,但是没有太多关于并行运行其中两种方法的文档。我的建议是一次坚持一种方法。

然而,根据我所读到的,如果您确实需要的话,应该可以同时实现两个或更多的方法。

那么有什么可用的呢?如果您查看 NuGet,您会看到列出了以下方法:无状态、表单和令牌。这些方法各有优缺点,但更重要的是,每种方法都是为特定场景设计的,您将在下面看到。

表单认证

我们先从最简单的开始。如果你习惯了 ASP.NET 的任何东西,那应该是表单验证。

自从最初的网络表单实现以来,表单身份验证一直是(现在仍然是)绝大多数。基于. NET 的 web 应用是在过去 10 年中构建的。

因此,Nancy 以与你习惯的其他实现完全相同的方式实现这一点并不奇怪。在本章的后面,我将通过一个例子来演示如何使用它,但是现在,如果您习惯于为人类使用的网站使用和配置它,那么这应该是您在 Nancy 应用中的首选。

它的主要动机是交互式网站和网络应用,由操作员通过标准的网络浏览器风格的界面进行交互。

无状态认证

无状态身份验证主要是为基于 API 的应用或需要对每个请求进行检查和身份验证的应用而设计的。

作为该方法预期用途的一个例子,NancyFX 文档给出了一个 API 端点,其访问密钥在请求头中提供。但是,您可以将其用于许多其他服务方法。

它的使用非常简单;与其他方法不同,除了需要执行简单的处理程序来使事情正常工作之外,没有其他配置。

每次调用使用方法保护的终结点时,都会调用您定义为使用方法的处理程序。然后,如果请求不被允许,处理程序应该返回空值,如果请求被认为有效并且应该被允许,处理程序应该返回一个 NancyIUserIdentity(我们稍后将了解更多这方面的信息)。

令牌认证

Token 方法可能是最复杂的理解方法。

该文档指出,它的主要用途是用于多个消费客户端,例如移动应用、单页应用和其他应该允许对未来请求进行身份验证的应用,但是一旦用户第一次成功进行身份验证,就不必重新查询后端数据存储。

在 NancyFX 维基上有一个更好的关于它的预期用途的描述,所以我不会在这里深入描述。

该方法的工作原理是在 Nancy 应用中保留一个双密钥存储,并针对每个受保护的资源进行检查。

初始用户身份验证由应用编写器提供的自定义方法执行。一旦给定用户第一次执行此操作,随后的请求就只使用该方法生成的令牌,这些令牌在超时前的任何给定 24 小时内有效。

使用 Nancy 的认证服务

不管您选择哪种身份验证方法,或者即使您使用自己的自定义提供程序,它们都有一些共同的概念。

第一个概念是用户的概念。Nancy 了解什么是用户,以及将用户作为请求的一部分的想法是如何工作的。

在您的 Nancy 应用中任何可以访问NancyContext的地方,您都可以读取一个名为CurrentUser的属性。

如果CurrentUser属性为空,则该请求不被视为已认证请求。如果该属性附加了一个从 Nancy 核心中实现的IUserIdentity接口派生的对象,则该请求被视为已通过身份验证。

至少,CurrentUser属性应该实现一个包含用户名的字符串和一个包含用户声明列表的可枚举字符串列表。

如果设置了这些,那么您可以通过在请求中附加一个Before管道过滤器来开始保护您的应用;在这个过滤器中,您只需要检查当前用户并采取相应的行动。在接下来的几章中,你会发现更多关于这些的信息。

Nancy 通过提供一些专门用于处理应用身份验证需求的预制扩展方法,使得添加这个过滤器变得更加简单。

其中最简单的是RequiresAuthentication,如果用户属性为空,它将回调403 (Not Authorized)的 HTTP 响应。

您还可以拥有RequiresClaims,它允许您将声明列表与用户身份提供的声明列表进行匹配,允许您根据用户的能力提供不同级别的访问。请注意,如果您使用除RequiresAuthentication之外的任何东西,大部分都会先调用这个,所以即使您可以指定多个,在大多数情况下,您通常也不必显式调用RequiresAuthentication

RequiresAnyClaim类似于RequiresClaims,区别在于至少要有一个匹配,而不是全部匹配。例如,如果您的用户有管理员评论者用户,而您要求RequiresClaims列出管理员用户,那么这两个声明都必须存在。RequestAnyClaim将在管理员或用户(或两者)在场时进行验证。

RequiresValidatedClaims根据用户附带的声明列表进行验证,但它需要一个指向实时检查和验证声明的函数的 lambda,而不是比较现有数据。

最后一个扩展RequiresHttps只允许通过安全的 HTTP 连接调用模块,否则将拒绝访问。

在定义路由之前,这些扩展中的每一个都会在构造函数中的 Nancy 路由模块上被调用,并且可以调用多个扩展。您在基础级别需要的一切都在Nancy.Security中定义。

有了这些信息,保护应用就变成了向模块中添加一个或多个扩展方法,如下面的代码所示:

代码清单 47

          using Nancy;
          using Nancy.Security;

          namespace nancybook.modules
          {
            public class AuthRoutes : NancyModule
            {
              public AuthRoutes() : base("/auth")
              {
                this.RequiresAuthentication();

                Get[@"/"] = _ => View["auth/index"];
              }
            }
          }

在这种情况下,只需要一个经过身份验证的用户在场;您可以根据需要添加任意多的其他组件来控制应用中所需的安全级别。

一旦保护好模块,您就需要安装并设置三种可用的身份验证方法之一,或者编写自己的方法。

在本章的剩余部分,我将向您展示如何启动和运行表单身份验证。

形成认证示例

一旦理解了一切是如何工作的,实现表单身份验证就非常简单了。但是,您需要创建一个自定义的引导类,我们还没有介绍过。

我们将在下一章中详细了解 Bootstrap 类到底是什么;目前,只需按照说明创建它,一切很快就会揭晓。

打开你的 NuGet 包管理器,搜索并安装 Nancy.Authentication.Forms .一旦你安装了这个,在你的应用的根目录下创建一个名为CustomBootstrapper.cs的新类,并确保它有以下内容(记得根据你的项目需要调整名称空间之类的东西):

代码清单 48

          using Nancy;
          using Nancy.Authentication.Forms;
          using Nancy.Bootstrapper;
          using Nancy.TinyIoc;

          namespace nancybook
          {
            public class CustomBootstrapper : DefaultNancyBootstrapper
            {
              protected override void ConfigureRequestContainer(
                TinyIoCContainer container,
                NancyContext context)
              {
                base.ConfigureRequestContainer(container, context);
                container.Register<IUserMapper, FakeDatabase>();
              }

              protected override void RequestStartup(
                TinyIoCContainer container,
                IPipelines pipelines,
                NancyContext context)
              {
                base.RequestStartup(container, pipelines, context);

                var formsAuthConfiguration = new FormsAuthenticationConfiguration
                {
                  RedirectUrl = "~/account/login",
                  UserMapper = container.Resolve<IUserMapper>()
                };

                FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
              }

            }
          }

保存并关闭此引导程序类;我们现在不需要用它做任何其他事情。

现在您需要创建一个类,该类为要查询的身份验证调用提供一个IUserIdentity作为结果。在这种情况下,我们已经告诉 Nancy,我们将创建一个FakeDatabase类来提供这些信息。实际上,我们将实现与后端数据存储通信的完整方式,并找到合适的用户标识。出于我们的目的,我们将创建一个静态用户列表,并允许我们的应用对此进行查询。

创建一个名为FakeDatabase.cs的新类,并确保其中包含以下代码:

代码清单 49

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using Nancy;
          using Nancy.Authentication.Forms;
          using Nancy.Security;

          namespace nancybook
          {
            public class DatabaseUser : IUserMapper
            {
              private List<FakeDatabaseUser> _users = new List<FakeDatabaseUser>
              {
                new FakeDatabaseUser
                {
                  UserId = new Guid(01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11),
                  UserName = "admin",
                  Claims = new List<string> {"Admin", "Reader", "Writer"},
                  Password = "admin"
                },
                new FakeDatabaseUser
                {
                  UserId = new Guid(02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12),
                  UserName = "alice",
                  Claims = new List<string> {"Reader", "Writer"},
                  Password = "letmeout"
                },
                new FakeDatabaseUser
                {
                  UserId = new Guid(03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13),
                  UserName = "bob",
                  Claims = new List<string> {"Reader"},
                  Password = "letmein"
                }
              };

              public IEnumerable<FakeDatabaseUser> Users { get { return _users; }}

              public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
              {
                var user = _users.FirstOrDefault(x => x.UserId == identifier);
                return user == null
                  ? null
                  : new AuthenticatedUser
                  {
                    UserName = user.UserName,
                    Claims = user.Claims
                  };
              }
            }
          }

我们还需要FakeDatabaseUserAuthenticatedUser的课程:

FakeDatabaseUser.cs

代码清单 50

          using System;
          using System.Collections.Generic;

          namespace nancybook
          {
            public class FakeDatabaseUser
            {
              public Guid UserId { get; set; }
              public string UserName { get; set; }
              public List<string> Claims { get; set; }
              public string Password { get; set; }
            }
          }

authenticationduster . cs

代码清单 51

          using System.Collections.Generic;
          using Nancy.Security;

          namespace nancybook
          {
            public class AuthenticatedUser : IUserIdentity
            {
              public string UserName { get; set; }
              public IEnumerable<string> Claims { get; set; }
            }
          }

有了这三个类,并设置了自定义引导程序,我们现在应该有了将身份验证添加到应用中所需的一切。

为了从实现用户输入登录凭证的表单的 HTML 视图中接收登录信息,我们需要创建一个合适的视图模型。我把它定义为:

代码清单 52

          using System.ComponentModel.DataAnnotations;

          namespace nancybook.Models
          {
            public class LoginParams
            {
              [Required]
              public string LoginName { get; set; }

              [Required]
              public string Password { get; set; }

            }
          }

在一个名为 LoginParams.cs 的文件中,没有要求以任何特定的方式对此进行建模。这取决于您从实际客户端体验中提供什么;您需要记住的是,您在后端的用户身份必须有一个 GUID 作为其存储数据的一部分,并且当您验证登录表单时,您必须能够检索该 GUID 并将其传递给 Nancy。

我将让您创建自己的视图来实现所需的各种屏幕,但是您需要一个提供登录表单,一个显示登录失败消息。您还需要应用安全部分的视图,该视图将在用户成功登录时显示。

一旦有了视图,您最不需要的就是几个 Nancy 路由模块。第一个是你的安全区域:

代码清单 53

          using Nancy;
          using Nancy.Security;

          namespace nancybook.modules
          {
            public class AuthRoutes : NancyModule
            {
              public AuthRoutes() : base("/auth")
              {
                this.RequiresAuthentication();

                Get[@"/"] = _ => View["auth/index"];
              }
            }
          }

第二个模块是为用户提供登录和注销系统能力的途径:

代码清单 54

          using System.Linq;
          using nancybook.Models;
          using Nancy;
          using Nancy.Authentication.Forms;
          using Nancy.ModelBinding;

          namespace nancybook.modules
          {
            public class AccountRoutes : NancyModule
            {
              public AccountRoutes() : base("/account")
              {
                Get[@"/login"] = _ => View["account/login"];

                Post[@"/login"] = _ =>
                {
                  var loginParams = this.Bind<LoginParams>();
                  FakeDatabaseUser user;

                  FakeDatabaseUser db = new FakeDatabaseUser();

                  user = db.Users.FirstOrDefault(
                    x =>
                      x.UserName.Equals(loginParams.LoginName) &&   
                      x.Password.Equals(loginParams.Password));

                  if(user== null)
                  {
                    return View["account/loginerror"];
                  }

                  return this.Login(user.UserId, fallbackRedirectUrl: "~/auth");
                };

                Get[@"/logout"] = _ => this.LogoutAndRedirect("~/account/login");
              }
            }
          }

此时,您应该能够构建您的项目。浏览 /auth 应该会触发登录页面,允许你在假数据库类中输入一个用户名和密码,这反过来应该允许你访问Auth模块中的路由。

总结

在这一章中,您学习了 Nancy 的身份验证功能是如何工作的,并看到以任何需要的方式实现身份验证都非常容易。

您还了解到,已经为实现定义了三种常见的场景和身份验证方法,并准备好使用 NuGet 进行安装。

在下一章中,我们将开始了解驱动 NancyFX 的发动机罩。我们将学习什么是自定义引导类,以及如何使用它。