Cred:可插拔身份验证¶
目标¶
Cred 是一个用于服务器的可插拔身份验证系统。它允许任意数量的网络协议连接并验证到系统,并与系统中对特定协议有意义的方面进行通信。例如,Twisted 的 POP3 支持传递一组“用户名和密码”凭据以获取指定电子邮件帐户的邮箱。IMAP 也是如此,但它检索同一邮箱的略微不同的视图,从而启用 IMAP 中特定于 IMAP 的功能,而这些功能在其他邮件协议中不可用。
Cred 的设计目的是允许在部署期间决定业务逻辑的后端实现(称为化身)和身份验证数据库(称为凭据检查器)。例如,同一个 POP3 服务器应该能够针对本地 UNIX 密码数据库或 LDAP 服务器进行身份验证,而无需了解邮件存储方式或位置。
为了概述其工作原理,一个“领域”对应于一个应用程序域,并负责化身,化身是可通过网络访问的业务逻辑对象。为了将它连接到身份验证数据库,一个名为Portal
的顶级对象存储一个领域和多个凭据检查器。想要登录的任何东西,例如一个Protocol
,都存储对门户的引用。登录包括将凭据和请求接口(例如 POP3 的IMailboxPOP3
)传递给门户。门户将凭据传递给相应的凭据检查器,凭据检查器返回一个化身 ID。该 ID 被传递给领域,领域返回相应的化身。对于具有创建邮箱对象的领域的 Portal 和检查 /etc/passwd 的凭据检查器,登录包括将用户名/密码和 IMailboxPOP3 接口传递给门户。门户将此传递给 /etc/passwd 凭据检查器,获取与电子邮件帐户相对应的化身 ID,将其传递给领域,并获取该电子邮件帐户的邮箱对象。
将所有这些放在一起,以下是如何处理登录请求的典型流程
Cred 对象¶
门户¶
这是登录的核心,是 Cred 系统中所有对象之间的集成点。Portal 只有一个具体实现,没有接口 - 它执行一个非常简单的任务。一个Portal
将一个 (1) 领域与一组 CredentialChecker 实例相关联。(稍后将详细介绍这些实例。)
如果您正在编写需要针对某些内容进行身份验证的协议,则需要引用 Portal,而不需要引用其他任何内容。它只有 2 个方法 -
login
(credentials, mind, *interfaces)
文档字符串非常详细(参见
twisted.cred.portal
),但简而言之,这就是您需要调用以将用户连接到系统的方法。通常您只传递一个接口,并且 mind 为None
。接口是返回的化身预期实现的可能接口,按优先级排序。结果是一个 Deferred,它触发一个包含以下内容的元组:化身实现的接口(它是传递给
*interfaces
元组的接口之一)实现该接口的对象(化身)
logout,一个 0 个参数的可调用对象,它断开此登录调用建立的连接
当化身注销时,必须调用 logout 方法。对于 POP3,这意味着在协议断开连接或注销时,等等。
registerChecker
(checker, *credentialInterfaces)
它将 CredentialChecker 添加到门户。可选的接口列表是检查器能够检查的凭据的接口。
凭据检查器¶
这是一个实现ICredentialsChecker
的对象,它将一些凭据解析为化身 ID。
无论凭据存储在内存数据结构、Apache 风格的 htaccess 文件、UNIX 密码数据库、SSH 密钥数据库还是任何其他形式中,ICredentialsChecker
的实现都是将这些数据连接到 Cred 的方式。
凭据检查器通过指定一个 credentialInterfaces 属性来规定其可以检查的凭据的一些要求,该属性是一个接口列表。传递给其 requestAvatarId 方法的凭据必须实现这些接口之一。
在大多数情况下,这些东西只会检查用户名和密码并生成用户名作为结果,但希望我们很快就能看到一些基于公钥、质询-响应和证书的凭据检查器机制。
如果凭据检查器无法验证用户,则应引发错误,并返回 twisted.cred.checkers.ANONYMOUS
以进行匿名访问。
凭据¶
奇怪的是,它代表用户提供的某些凭据。通常这只是一个小的静态数据块,但在某些情况下,它实际上是一个连接到网络协议的对象。例如,用户名/密码对是静态的,但质询/响应服务器是一个活动的有限状态机,需要多次方法调用才能确定结果。
Twisted 在twisted.cred.credentials
模块中提供了一些凭据接口和实现,例如IUsernamePassword
和IUsernameHashedPassword
。
领域¶
领域是一个接口,它将您的“业务对象”宇宙连接到身份验证系统。
IRealm
是另一个单方法接口
requestAvatar
(avatarId, mind, *interfaces)
此方法通常从“Portal.login”调用。avatarId 是由 CredentialChecker 返回的。
注意
请注意,
avatarId
必须始终是字符串。特别是,不要使用 Unicode 字符串。如果需要国际化支持,建议使用 UTF-8,并在领域中处理解码。关于此方法,重要的是要认识到,如果它被调用,用户已经通过身份验证。因此,如果可能,领域应该在可能的情况下创建一个新用户(如果不存在)。当然,有时在没有更多信息的情况下,这将是不可能的,这就是接口参数的作用。
由于 requestAvatar 应该从 Deferred 回调中调用,因此它可能返回一个 Deferred 或同步结果。
头像¶
头像是一个特定用户的业务逻辑对象。对于 POP3,它是一个邮箱,对于第一人称射击游戏,它是与游戏交互的对象,即演员。头像特定于应用程序,每个头像代表一个唯一的“用户”。
思维¶
如前所述,思维通常是 None
,因此如果您愿意,可以跳过此部分。
Perspective Broker 的大师们已经知道这个对象是错误命名的“客户端对象”。没有“思维”类,甚至没有接口,但它是一个发挥重要作用的对象 - 任何要转发给已认证客户端的通知都将通过“思维”传递。此外,它允许在登录期间除了头像 ID 之外,将更多信息传递给领域。
这个名字可能看起来很不寻常,但考虑到思维代表网络连接“另一端”的实体,它既接收更新又发出命令,我认为它是合适的。
虽然许多协议不会使用它,但它起着重要的作用。它作为 Portal 和 Realm 的参数提供,尽管 CredentialChecker 应该仅通过 Credentials 实例与客户端程序交互。
与原始的 Perspective Broker “客户端对象”不同,思维的实现通常由连接的协议而不是 Realm 决定。需要特定接口来发出通知的 Realm 需要用适配器包装协议的思维实现,以获得符合其预期接口的实现 - 然而,Perspective Broker 可能会继续使用客户端对象具有预先指定远程接口的模型。
(如果您不太理解这一点,没关系。它很难解释,而且在 cred 的简单用法中没有使用,因此您可以随意传递 None,直到您发现自己需要类似的东西。)
职责¶
服务器协议实现¶
协议实现者应该定义头像应该实现的接口,并将协议设计为具有附加的 Portal。当用户使用协议登录时,将创建一个凭据对象,传递给 Portal,并请求具有适当接口的头像。当用户注销或协议断开连接时,应该注销头像。
协议设计者不应硬编码用户的身份验证方式或实现的领域。例如,POP3 协议实现需要一个 Portal,其领域返回实现 IMailbox 的头像,并且其凭据检查器接受用户名/密码凭据,但仅此而已。以下是代码可能的外观草图 - 请注意,USER 和 PASS 是用于登录的协议命令,而 DELE 命令只能在您登录后使用
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from zope.interface import Interface
from twisted.cred import credentials, error
from twisted.internet import defer
from twisted.protocols import basic
from twisted.python import log
class IMailbox(Interface):
"""
Interface specification for mailbox.
"""
def deleteMessage(index):
pass
class POP3(basic.LineReceiver):
# ...
def __init__(self, portal):
self.portal = portal
def do_DELE(self, i):
# uses self.mbox, which is set after login
i = int(i) - 1
self.mbox.deleteMessage(i)
self.successResponse()
def do_USER(self, user):
self._userIs = user
self.successResponse("USER accepted, send PASS")
def do_PASS(self, password):
if self._userIs is None:
self.failResponse("USER required before PASS")
return
user = self._userIs
self._userIs = None
d = defer.maybeDeferred(self.authenticateUserPASS, user, password)
d.addCallback(self._cbMailbox, user)
def authenticateUserPASS(self, user, password):
if self.portal is not None:
return self.portal.login(
credentials.UsernamePassword(user, password), None, IMailbox
)
raise error.UnauthorizedLogin()
def _cbMailbox(self, ial, user):
interface, avatar, logout = ial
if interface is not IMailbox:
self.failResponse("Authentication failed")
log.err("_cbMailbox() called with an interface other than IMailbox")
return
self.mbox = avatar
self._onLogout = logout
self.successResponse("Authentication succeeded")
log.msg("Authenticated login for " + user)
应用程序实现¶
应用程序开发人员可以实现领域和凭据检查器。例如,他们可能会实现一个领域,该领域返回实现 IMailbox 的头像,使用 MySQL 进行存储,或者可能是一个使用 LDAP 进行身份验证的凭据检查器。在以下示例中,为简单的远程对象服务(使用 Twisted 的 Perspective Broker 协议)实现了 Realm
from zope.interface import implementer
from twisted.spread import pb
from twisted.cred.portal import IRealm
class SimplePerspective(pb.Avatar):
def perspective_echo(self, text):
print('echoing',text)
return text
def logout(self):
print(self, "logged out")
@implementer(IRealm)
class SimpleRealm:
def requestAvatar(self, avatarId, mind, *interfaces):
if pb.IPerspective in interfaces:
avatar = SimplePerspective()
return pb.IPerspective, avatar, avatar.logout
else:
raise NotImplementedError("no interface")
部署¶
部署涉及将协议、适当的领域和凭据检查器绑定在一起。例如,POP3 服务器可以通过附加一个 Portal 来构建,该 Portal 包装基于 MySQL 的领域和 /etc/passwd 凭据检查器,或者如果更有用,可能是 LDAP 凭据检查器。以下示例显示了如何使用内存中凭据检查器部署上一个示例中的 SimpleRealm
from twisted.spread import pb
from twisted.internet import reactor
from twisted.cred.portal import Portal
from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
portal = Portal(SimpleRealm())
checker = InMemoryUsernamePasswordDatabaseDontUse()
checker.addUser("guest", "password")
portal.registerChecker(checker)
reactor.listenTCP(9986, pb.PBServerFactory(portal))
reactor.run()
Cred 插件¶
使用 Cred 插件进行身份验证¶
Cred 为身份验证方法提供插件架构。此架构的主要 API 是命令行;插件旨在由最终用户在部署 TAP(twistd 插件)时指定。
有关编写 twistd 插件和在您的应用程序中使用 cred 插件的更多信息,请参阅 编写 twistd 插件 文档。
构建 Cred 插件¶
要为 cred 构建插件,您应该首先定义一个 authType
,这是一个简短的单字字符串,用于将您的插件定义到命令行。有了它,惯例是在 twisted.plugins
模块路径中创建一个名为 myapp_plugins.py
的文件。
以下是定义此类插件的应用程序的文件结构示例
MyApplication/
setup.py
myapp/
__init__.py
cred.py
server.py
twisted/
plugins/
myapp_plugins.py
在您的应用程序中创建此结构后,您可以通过构建实现 ICheckerFactory
的工厂类来创建 Cred 插件的代码。这些工厂类不应包含大量的代码。大多数真正的应用程序逻辑应该驻留在 Cred 检查器本身中。(有关构建这些检查器的帮助,请向上滚动。)
CheckerFactory 的核心目的是将命令行上传递的 argstring
转换为适合 Checker 类的初始化参数集。在大多数情况下,这应该只是构建一个字典或参数元组,然后将它们传递给新的检查器实例。
from zope.interface import implementer
from twisted import plugin
from twisted.cred.strcred import ICheckerFactory
from myapp.cred import SpecialChecker
# The class needs to implement both of these interfaces
# for the plugin system to find our factory.
@implementer(ICheckerFactory, plugin.IPlugin)
class SpecialCheckerFactory(object):
"""
A checker factory for a specialized (fictional) API.
"""
# This tells AuthOptionsMixin how to find this factory.
authType = "special"
# This is a one-line explanation of what arguments, if any,
# your particular cred plugin requires at the command-line.
argStringFormat = "A colon-separated key=value list."
# This help text can be multiple lines. It will be displayed
# when someone uses the "--help-auth-type special" command.
authHelp = """Some help text goes here ..."""
# This will be called once per command-line.
def generateChecker(self, argstring=""):
argdict = dict((x.split('=') for x in argstring.split(':')))
return SpecialChecker(**argdict)
# We need to instantiate our class for the plugin to work.
theSpecialCheckerFactory = SpecialCheckerFactory()
有关您的插件如何在您的应用程序(以及其他应用程序开发人员)中使用的更多信息,请参阅 编写 twistd 插件 文档。
结论¶
阅读完本教程后,您应该能够
了解 Cred 架构如何应用于您的应用程序
将您的应用程序与 Cred 的对象模型集成
部署使用 Cred 进行身份验证的应用程序
允许您的用户使用命令行身份验证插件