认证和授权

安全导论

认证是系统安全地识别用户的机制。EVE支持几种认证方案:基本认证、令牌认证、HMAC认证。 OAuth2 integration 很容易完成。

授权是一种机制,通过该机制,系统可以确定特定(经过身份验证的)用户对系统控制的资源的访问级别。在EVE中,您可以限制对所有API端点的访问,或者仅对其中一些端点的访问。您可以保护一些HTTP谓词,而不让其他谓词打开。例如,您可以允许公共只读访问,同时将项目创建和版本仅限于授权用户。你也可以允许 GET 访问某些请求和 POST 通过检查方法参数来访问其他人。还支持基于角色的访问控制。

安全性是定制非常重要的领域之一。这就是为什么向您提供了一些基本身份验证类。它们实现了基本的认证机制,为了实现授权逻辑,必须对它们进行子类化。无论选择哪种身份验证方案,在子类中只需覆盖 check_auth() 方法。

全局身份验证

要为API启用身份验证,只需在应用程序实例化时传递自定义身份验证类。在我们的示例中,我们将使用 BasicAuth 基类,它实现 基本身份验证 方案:

from eve.auth import BasicAuth

class MyBasicAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource,
                   method):
        return username == 'admin' and password == 'secret'

app = Eve(auth=MyBasicAuth)
app.run()

您的所有API端点现在都是安全的,这意味着客户端需要提供正确的凭据才能使用API:

$ curl -i http://example.com
HTTP/1.1 401 UNAUTHORIZED
Please provide proper credentials.

$ curl -H "Authorization: Basic YWRtaW46c2VjcmV0" -i http://example.com
HTTP/1.1 200 OK

默认情况下,对所有HTTP谓词(方法)的所有端点的访问都受到限制,从而有效地锁定整个API。

但是,如果您的身份验证逻辑更加复杂,并且您只想保护某些端点或根据所使用的端点应用不同的逻辑,该怎么办呢?您只需向身份验证类添加逻辑就可以了,可能只需如下所示:

class MyBasicAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        if resource in ('zipcodes', 'countries'):
            # 'zipcodes' and 'countries' are public
            return True
        else:
            # all the other resources are secured
            return username == 'admin' and password == 'secret'

如果需要,这种方法还允许接受请求 method 考虑,例如允许 GET 在强制验证编辑时请求所有人 (POSTPUTPATCHDELETE

终结点级身份验证

这个 一个类将它们全部绑定 上面提到的方法可能对大多数用例都是好的,但是一旦授权逻辑变得更复杂,就很容易导致复杂和不可管理的代码,这是您在处理安全性时真正不希望看到的。

如果我们可以有专门的auth类,可以自由地应用于选定的端点,那不是很好吗?这样,全局级别的auth类(如上所示传递给eve构造函数的类)在所有端点上仍然是活动的,除非需要不同的授权逻辑。或者,我们甚至可以选择 not 提供一个全局身份验证类,有效地使所有端点都成为公共端点,我们希望保护的端点除外。有了这样的系统,我们甚至可以选择让一些端点受到保护,比如说,基本身份验证,而其他端点则受到令牌或HMAC身份验证的保护!

事实证明,只要启用资源级别就可以实现这一点 authentication 定义API时的设置 domain .

DOMAIN = {
    'people': {
        'authentication': MySuperCoolAuth,
        ...
        },
    'invoices': ...
    }

就这样。这个 people 终结点现在将使用 MySuperCoolAuth 类进行身份验证,而 invoices 端点将使用通用身份验证类(如果提供),否则它将只对公众开放。

您可以使用其他特性和选项来降低身份验证类的复杂性,尤其是在使用全局级身份验证系统时(但不仅如此)。让我们回顾一下。

全局终结点安全性

您可能需要一个公共只读API,其中只有授权用户才能写入、编辑和删除。你可以通过使用 PUBLIC_METHODSPUBLIC_ITEM_METHODS global settings .将以下内容添加到 settings.py

PUBLIC_METHODS = ['GET']
PUBLIC_ITEM_METHODS = ['GET']

运行你的API。post、patch和delete仍然受到限制,而get在所有api端点都是公开可用的。 PUBLIC_METHODS 引用资源终结点,例如 /people ,同时 PUBLIC_ITEM_METHODS 指单个项目,如 /people/id .

自定义终结点安全性

假设您希望只允许公共读取访问某些资源。您可以在资源级别声明公共方法,同时声明API domain

DOMAIN = {
    'people': {
        'public_methods': ['GET'],
        'public_item_methods': ['GET'],
        },
    }

请注意,当出现时, resource settings 覆盖全局设置。你可以利用这一点。假设您希望授予对所有端点的读取访问权限,但只有 /invoices .首先打开所有端点的读取访问:

PUBLIC_METHODS = ['GET']
PUBLIC_ITEM_METHODS = ['GET']

然后保护私有端点:

DOMAIN = {
    'invoices': {
        'public_methods': [],
        'public_item_methods': [],
        }
    }

有效制造 invoices 受限制的资源。

基本身份验证

这个 eve.auth.BasicAuth 类允许实现基本身份验证(rfc2617)。为了实现自定义身份验证,应该对它进行子类化。

使用bcrypt进行基本身份验证

密码编码方式 bcrypt 是个好主意。它以牺牲性能为代价,但这正是关键所在,因为缓慢的编码意味着很好地抵抗蛮力攻击。要获得更快(更不安全)的选择,请参阅下面的sha1/mac片段。

此脚本假定用户帐户存储在 accounts MongoDB集合,密码存储为bcrypt散列。除非明确公开,否则所有API资源/方法都将受到保护。

请注意

您需要安装 py-bcrypt 为了这个工作。

# -*- coding: utf-8 -*-

"""
    Auth-BCrypt
    ~~~~~~~~~~~

    Securing an Eve-powered API with Basic Authentication (RFC2617).

    You will need to install py-bcrypt: ``pip install py-bcrypt``

    This snippet by Nicola Iarocci can be used freely for anything you like.
    Consider it public domain.
"""

import bcrypt
from eve import Eve
from eve.auth import BasicAuth
from flask import current_app as app

class BCryptAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        # use Eve's own db driver; no additional connections/resources are used
        accounts = app.data.driver.db['accounts']
        account = accounts.find_one({'username': username})
        return account and \
            bcrypt.hashpw(password, account['password']) == account['password']


if __name__ == '__main__':
    app = Eve(auth=BCryptAuth)
    app.run()

使用sha1/hmac进行基本身份验证

此脚本假定用户帐户存储在 accounts MongoDB集合,密码存储为sha1/hmac散列。除非明确公开,否则所有API资源/方法都将受到保护。

# -*- coding: utf-8 -*-

"""
    Auth-SHA1/HMAC
    ~~~~~~~~~~~~~~

    Securing an Eve-powered API with Basic Authentication (RFC2617).

    Since we are using werkzeug we don't need any extra import (werkzeug being
    one of Flask/Eve prerequisites).

    This snippet by Nicola Iarocci can be used freely for anything you like.
    Consider it public domain.
"""

from eve import Eve
from eve.auth import BasicAuth
from werkzeug.security import check_password_hash
from flask import current_app as app

class Sha1Auth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        # use Eve's own db driver; no additional connections/resources are used
        accounts = app.data.driver.db['accounts']
        account = accounts.find_one({'username': username})
        return account and \
            check_password_hash(account['password'], password)


if __name__ == '__main__':
    app = Eve(auth=Sha1Auth)
    app.run()

基于令牌的身份验证

基于令牌的身份验证可以视为基本身份验证的专用版本。授权头标记将包含身份验证令牌作为用户名,没有密码。

此脚本假定用户帐户存储在 accounts MongoDB集合。所有API资源/方法都将受到保护,除非它们显式公开(通过修改某些设置,您可以打开一个或多个资源和/或方法进行公共访问-请参阅文档)。

# -*- coding: utf-8 -*-

"""
    Auth-Token
    ~~~~~~~~~~

    Securing an Eve-powered API with Token based Authentication.

    This snippet by Nicola Iarocci can be used freely for anything you like.
    Consider it public domain.
"""

from eve import Eve
from eve.auth import TokenAuth
from flask import current_app as app

class TokenAuth(TokenAuth):
    def check_auth(self, token, allowed_roles, resource, method):
        """For the purpose of this example the implementation is as simple as
        possible. A 'real' token should probably contain a hash of the
        username/password combo, which should then be validated against the account
        data stored on the DB.
        """
        # use Eve's own db driver; no additional connections/resources are used
        accounts = app.data.driver.db['accounts']
        return accounts.find_one({'token': token})


if __name__ == '__main__':
    app = Eve(auth=TokenAuth)
    app.run()

HMAC身份验证

这个 eve.auth.HMACAuth 类允许自定义、类似于AmazonS3的HMAC(哈希消息身份验证代码)身份验证,这基本上是基于 Authorization 标题。

HMAC身份验证的工作原理

服务器通过一些带外技术向客户机提供用户ID和密钥(例如,服务向客户机发送一封包含用户ID和密钥的电子邮件)。客户端将使用提供的密钥对所有请求进行签名。

当客户机想要发送一个请求时,他构建完整的请求,然后使用密钥,在完整的消息体上计算散列(如果需要,还可以选择一些消息头)。

接下来,客户机将计算出的哈希和他的用户ID添加到授权头中的消息中:

Authorization: johndoe:uCMfSzkjue+HSDygYB5aEg==

把它送到服务中心。服务从消息头检索用户ID,并在其自己的数据库中搜索该用户的私钥。接下来,它使用键在消息体(和选定的头)上计算散列来生成散列。如果客户机发送的哈希与服务器计算的哈希匹配,那么服务器就知道消息是由真正的客户机发送的,并且不会以任何方式进行更改。

实际上,唯一棘手的部分是与用户共享一个密钥,并确保其安全。这就是为什么有些服务允许在有限的生命周期内生成共享密钥,这样您就可以将密钥交给第三方以临时代表您工作。这也是为什么密钥通常是通过带外频道(通常是网页,如上所述,是电子邮件或普通的旧纸)提供的原因。

这个 eve.auth.HMACAuth 类还支持访问角色。

HMAC示例

下面的代码段也可以在 examples/security folder of the Eve repository .

from eve import Eve
from eve.auth import HMACAuth
from flask import current_app as app
from hashlib import sha1
import hmac


class HMACAuth(HMACAuth):
    def check_auth(self, userid, hmac_hash, headers, data, allowed_roles,
                   resource, method):
        # use Eve's own db driver; no additional connections/resources are
        # used
        accounts = app.data.driver.db['accounts']
        user = accounts.find_one({'userid': userid})
        if user:
            secret_key = user['secret_key']
        # in this implementation we only hash request data, ignoring the
        # headers.
        return user and \
            hmac.new(str(secret_key), str(data), sha1).hexdigest() == \
                hmac_hash


if __name__ == '__main__':
    app = Eve(auth=HMACAuth)
    app.run()

基于角色的访问控制

上面的代码片段故意忽略 allowed_roles 参数。您可以使用此参数限制对也被分配了特定角色的已验证用户的访问。

首先,你要用新的 ALLOWED_ROLESALLOWED_ITEM_ROLES global settings (或相应的 allowed_rolesallowed_item_roles resource settings

ALLOWED_ROLES = ['admin']

然后,您的子类将通过充分利用前面提到的方法来实现授权逻辑。 allowed_roles 参数。

下面的代码段假定用户帐户存储在 accounts mongodb集合,密码存储为sha1/hmac散列,用户角色存储在“roles”数组中。除非明确公开,否则所有API资源/方法都将受到保护。

# -*- coding: utf-8 -*-

"""
    Auth-SHA1/HMAC-Roles
    ~~~~~~~~~~~~~~~~~~~~

    Securing an Eve-powered API with Basic Authentication (RFC2617) and user
    roles.

    Since we are using werkzeug we don't need any extra import (werkzeug being
    one of Flask/Eve prerequisites).

    This snippet by Nicola Iarocci can be used freely for anything you like.
    Consider it public domain.
"""

from eve import Eve
from eve.auth import BasicAuth
from werkzeug.security import check_password_hash
from flask import current_app as app

class RolesAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        # use Eve's own db driver; no additional connections/resources are used
        accounts = app.data.driver.db['accounts']
        lookup = {'username': username}
        if allowed_roles:
            # only retrieve a user if his roles match ``allowed_roles``
            lookup['roles'] = {'$in': allowed_roles}
        account = accounts.find_one(lookup)
        return account and check_password_hash(account['password'], password)


if __name__ == '__main__':
    app = Eve(auth=RolesAuth)
    app.run()

用户限制的资源访问

启用此功能后,每个存储的文档都与创建它的帐户关联。这允许API在所有类型的请求上透明地只提供帐户创建的文档:读取、编辑、删除,当然还有创建。需要启用用户身份验证才能正常工作。

在全局级别,此功能通过设置启用 AUTH_FIELD 通过设置本地(在端点级别) auth_field .这些属性定义用于存储创建文档的用户ID的字段的名称。例如,通过设置 AUTH_FIELDuser_id ,您将有效地(并且对用户透明地)添加 user_id 每个存储文档的字段。这将用于检索/编辑/删除用户存储的文档。

但是你怎么设置 auth_field 价值?通过调用 set_request_auth_value() 类方法。让我们从上面修改bcrypt身份验证示例:

 # -*- coding: utf-8 -*-

 """
     Auth-BCrypt
     ~~~~~~~~~~~

     Securing an Eve-powered API with Basic Authentication (RFC2617).

     You will need to install py-bcrypt: ``pip install py-bcrypt``

     This snippet by Nicola Iarocci can be used freely for anything you like.
     Consider it public domain.
 """

 import bcrypt
 from eve import Eve
 from eve.auth import BasicAuth


 class BCryptAuth(BasicAuth):
     def check_auth(self, username, password, allowed_roles, resource, method):
         # use Eve's own db driver; no additional connections/resources are used
         accounts = app.data.driver.db['accounts']
         account = accounts.find_one({'username': username})
         # set 'auth_field' value to the account's ObjectId
         # (instead of _id, you might want to use ID_FIELD)
         if account and '_id' in account:
             self.set_request_auth_value(account['_id'])
         return account and \
             bcrypt.hashpw(password, account['password']) == account['password']


 if __name__ == '__main__':
     app = Eve(auth=BCryptAuth)
     app.run()

身份验证驱动的数据库访问

自定义身份验证类还可以设置在服务活动请求时应使用的数据库。

通常,您要么为整个API使用一个数据库,要么通过设置配置每个端点使用的数据库 mongo_prefix 达到所需值(参见 资源/项目终结点

但是,您可以选择基于活动令牌、用户或客户机选择目标数据库。如果您的用例包括用户专用的数据库实例,那么这很方便。你所要做的就是设置调用 set_mongo_prefix() 方法对请求进行身份验证。

一个简单的例子是:

from eve.auth import BasicAuth

class MyBasicAuth(BasicAuth):
    def check_auth(self, username, password, allowed_roles, resource, method):
        if username == 'user1':
            self.set_mongo_prefix('MONGO1')
        elif username == 'user2':
            self.set_mongo_prefix('MONGO2')
        else:
            # serve all other users from the default db.
            self.set_mongo_prefix(None)
        return username is not None and password == 'secret'

app = Eve(auth=MyBasicAuth)
app.run()

以上课程将提供 user1 数据来自数据库,配置设置的前缀为 MONGO1 在里面 settings.py .同样的情况也发生在 user2MONGO2 而所有其他用户都使用默认数据库。

因为值由设置 set_mongo_prefix() 优先于默认级别和终结点级别 mongo_prefix 设置,这里发生的是,无论端点的最终数据库配置如何,都将始终从保留的数据库为这两个用户提供服务。

OAuth2集成

由于您可以完全控制授权过程,所以将OAuth2与EVE集成起来非常容易。熟悉本页中的主题,然后前往 Eve-OAuth2 ,一个利用 Flask-Sentinel 演示如何使用OAuth2保护API。

请注意

此页面中的代码段也可以在 examples/security folder of the Eve repository .