Masonite API

未匹配的标注
本文档最新版为 4.0,旧版本可能放弃维护,推荐阅读最新版!

Masonite API

简介

Masonite API 是一个旨在使添加具有各种身份验证和权限范围的外部 API 变得非常简单的包。有一个称为「API 资源」的新概念,可供您用来构建您的特定端点。在本文档中,我们将逐步介绍如何创建一个 User Resource(用户资源),以便我们可以逐步介绍各个活动部分。

安装

安装 PyPi 包:

$ pip install masonite-api

将服务提供者添加到 config/providers.py 中的提供者列表中:

from masonite.api.providers import ApiProvider

PROVIDERS = [
    #..

    # Third Party Providers
    ApiProvider,
]

添加防护(Guard)

Masonent API 附带了一个 api 防护,您可以用它来处理获取用户的操作,就像在普通 Web 应用中使用 request.user() 方法或 auth() 函数一样。

您可以添加如下配置到您的 config/auth.py 文件中:

    'guards': {
        'web': {
            # ..
            # Normal config here
            # ..
        },
        'api': {
            'driver': 'jwt',
            'model': User,
        },
    }

创建资源

我们可以通过在任意位置构建 API 资源来创建它们。在本文档中,我们会将它们放在 app/resources 目录中。我们只需要创建一个简单的资源类,让它继承自 api.resources.Resource 即可。

from masonite.api.resources import Resource

class UserResource(Resource):
    pass

您还应该通过导入并指定模型属性来指定一个模型:

from masonite.api.resources import Resource
from app.User import User

class UserResource(Resource):
    model = User

最后,为了简单起见,我们可以指定一个序列化器。它将接受我们返回的任何 Oator models 或 Collection,并将它们序列化到通过 JsonResponseMiddleware 获取的字典中。

from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer

class UserResource(Resource, JSONSerializer):
    model = User

太棒了!现在我们准备出发!

指定我们的路由

我们的资源将根据我们提供的信息构建多个路由,因此让我们将其导入到 web.py 文件中,以便它为我们构建路由。我们还可以指定将用于所有路由的基本路由。

...
from app.resources.UserResource import UserResource

ROUTES = [
    ...
    UserResource('/api/users').routes(),
    ...
]

这将建立一个路由列表,如下所示:

========  =============  =======  ========  ============
Method    Path           Name     Domain    Middleware
========  =============  =======  ========  ============
POST      /api/user
GET       /api/user
GET       /api/user/@id
PATCH/PUT /api/user/@id
DELETE    /api/user/@id
========  =============  =======  ========  ============

我们还可以通过设置 methods 属性来指定想要创建的路由。

from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']

这样只会根据指定的方法构建那些路由:

========  =============  =======  ========  ============
Method    Path           Name     Domain    Middleware
========  =============  =======  ========  ============
POST      /api/user
GET       /api/user
GET       /api/user/@id
========  =============  =======  ========  ============

添加中间件

您可以使用 middleware 方法指定中间件,从而轻松地将中间件添加到路由中:

...
from app.resources.UserResource import UserResource

ROUTES = [
    ...
    UserResource('/api/users').middleware('auth', 'managers').routes(),
    ...
]

或者,当然,您也可以将中间件添加到组中:

...
from app.resources.UserResource import UserResource
from masonite.routes import RouteGroup

ROUTES = [
    ...
    RouteGroup([
         UserResource('/api/users').routes(),
    ], middleware=('auth', 'managers'))
    ...
]

使用 Guard 中间件

覆盖方法

您可能希望覆盖 API 端点内部使用的一些方法,以返回必要的数据。

这些方法是:create、index、show、update、delete。

您可以查看有关如何使用这些方法以及如何修改它们的存储库,这很简单。 show 方法用于显示当端点类似于 /api/user 时返回的所有结果。

覆盖方法将类似于:

from masonite.api.resources import Resource
from masonite.request import Request
from app.User import User
from masonite.api.serializers import JSONSerializer

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']

    def show(self, request: Request):
        return self.model.find(request.param('id'))

这将不仅返回 active 为 1 的所有结果。还要记住,这些方法是通过容器解析的,因此我们可以使用依赖项注入:

from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.request import Request

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']

    def show(self, request: Request):
        return self.model.where('active', self.request.input('active')).get()

    def index(self):
        return self.model.all()

使用 POST /api/user 获取所有用户记录时,将运行 index 方法。

删除模型属性

当前,我们的回复可能类似于:

{
    "id": 1,
    "name": "username",
    "email": "email@email.com",
    "password": "12345",
    "remember_token": null,
    "created_at": "2018-09-23T07:33:30.118068",
    "updated_at": "2018-09-23T11:47:48.962105",
    "customer_id": null,
    "plan_id": null,
    "is_active": null
}

您可能不想显示所有模型属性,如 idemailpassword。我们可以选择使用 without 类属性删除它们:

from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.request import Request

class UserResource(Resource, JSONSerializer):
    model = User
    methods = ['create', 'index', 'show']
    without = ['id', 'email', 'password']

现在,我们的响应将如下所示:

{
    "name": "username",
    "remember_token": null,
    "created_at": "2018-09-23T07:33:30.118068",
    "updated_at": "2018-09-23T11:47:48.962105",
    "customer_id": null,
    "plan_id": null,
    "is_active": null
}

是的,就是这么简单。

身份验证

为了使任何 API 包都出色,确实需要具有强大而简单的身份验证。

JWT 身份验证

我们可以通过类继承的方式来为特定端点指定身份验证:

from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.api.authentication import JWTAuthentication

class UserResource(Resource, JSONSerializer, JWTAuthentication):
    model = User
    methods = ['create', 'index', 'show']

现在,我们的端点是 JWT 身份验证。如果我们现在在浏览器中通过向http://localhost:8000/api/user 发送 GET 请求来访问端点。
我们会看到:

{
    "error": "no API token found"
}

太棒了!如果我们通过访问 localhost:8000/api/user?token=1234 来指定令牌,则我们将看到不同的错误:

{
    "error": "token is invalid"
}

JWT 令牌

通过向我们的 web.py 添加一些路由,我们可以轻松创建一个端点来分发和刷新API令牌:

...
from app.resources.UserResource import UserResource
from masonite.api.routes import JWTRoutes

ROUTES = [
    ...
    UserResource('/api/user').routes(),
    JWTRoutes('/token'),
    ...
]

现在,我们可以使用您的 username 和 password 向 http:// localhost:8000/token 发出 POST 请求,这将使我们获得 JWT 令牌。

{% api-method method="post" host="localhost:8000" path="/token*
{% api-method-summary %}
检索 JWT 令牌
{% endapi-method-summary %}

{% api-method-description %}
使用此端点可以使用用户名和密码来检索新令牌。用户名和密码将默认为位于config/auth.py 中的常规身份验证模型。
{% endapi-method-description %}

{% api-method-spec %}
{% api-method-request %}
{% api-method-query-parameters %}
{% api-method-parameter name="username" type="string" required=true %}
使用您的身份验证模型进行身份验证的用户名
{% endapi-method-parameter %}

{% api-method-parameter name="password" type="string" required=true %}
The password to authenticate using your authentication model
{% endapi-method-parameter %}
{% endapi-method-query-parameters %}
{% endapi-method-request %}

{% api-method-response %}
{% api-method-response-example httpCode=200 %}
{% api-method-response-example-description %}

{% endapi-method-response-example-description %}

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3N1ZWQiOiIyMDE4LTA5LTIzVDE1OjA1OjM4LjE1MTQyMC0wNDowMCIsImV4cGlyZXMiOiIyMDE4LTA5LTIzVDE1OjEwOjM4LjE1MTY4Mi0wNDowMCIsInNjb3BlcyI6ZmFsc2V9.7BnD7Ro1oK2WdJOU4hsBY3KK0tojBszZAaazQ6MMK-4"
}

{% endapi-method-response-example %}
{% endapi-method-response %}
{% endapi-method-spec %}
{% endapi-method %}

我们现在可以使用这个令牌通过使用这个新令牌来进行调用。此令牌有效期为5分钟,过期后将需要刷新令牌。

刷新令牌

一旦我们的JWT令牌过期,我们需要通过将旧的过期令牌发送到

{% api-method method="post" host="localhost:8000" path="/token/refresh*
{% api-method-summary %}
来刷新它
{% endapi-method-summary %}
{% api-method-description %}
{% endapi-method-description %}
{% api-method-spec %}
{% api-method-request %}
{% api-method-path-parameters %}
{% api-method-parameter name="token" type="string" required=true %}
过期的 JWT 令牌
{% endapi-method-parameter %}
{% endapi-method-path-parameters %}
{% endapi-method-request %}

{% api-method-response %}
{% api-method-response-example httpCode=200 %}
{% api-method-response-example-description %}

{% endapi-method-response-example-description %}

{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3N1ZWQiOiIyMDE4LTA5LTIzVDE1OjA1OjM4LjE1MTQyMC0wNDowMCIsImV4cGlyZXMiOiIyMDE4LTA5LTIzVDE1OjEwOjM4LjE1MTY4Mi0wNDowMCIsInNjb3BlcyI6ZmFsc2V9.7BnD7Ro1oK2WdJOU4hsBY3KK0tojBszZAaazQ6MMK-4"
}

{% endapi-method-response-example %}
{% endapi-method-response %}
{% endapi-method-spec %}
{% endapi-method %}

权限范围

您还可以指定所需的任何权限范围。大多数情况下,某些 API 端点需要比其他 API 端点具有更多的限制范围。我们可以使用 scopes 属性指定需要的任何作用域,也可以继承另一个类。

from masonite.api.resources import Resource
from app.User import User
from masonite.api.serializers import JSONSerializer
from masonite.api.authentication import JWTAuthentication, PermissionScopes

class UserResource(Resource, JSONSerializer, JWTAuthentication, PermissionScopes):
    model = User
    methods = ['create', 'index', 'show']
    scopes = ['user:read']

现在,所有 API 端点都将需要具有正确的权限范围。现在访问此 API 端点结果是:

{
    "error": "Permission scope denied. Requires scopes: user:read"
}

我们可以通过使用 scopes 输入向端点发送 POST 请求来请求所需的作用域。作用域应该用逗号分隔。请求应该看起来像:

http://localhost:8000/token?scopes=user:read,user:create

这将生成具有正确权限范围的新令牌。

筛选器作用域

筛选器作用域是上述作用域的扩展。它将根据作用域级别过滤数据。如果您希望特定作用域比其他作用域具有更多权限,这非常有用。

为此,我们可以使用 FilterScopes 类扩展我们的资源:

from masonite.api.filters import FilterScopes

class UserResource(..., ..., FilterScopes):
    model = User
    methods = ['create', 'index', 'show']
    scopes = ['user:read']
    filter_scopes = {
        'user:read': ['name', 'email'],
        'user:manager': ['id', 'name', 'email', 'active', 'password']
    }

现在,当您发出此请求时,它将根据用户作用域返回列。

创建身份验证类

身份验证类是非常简单的类。它们只需要继承 BaseAuthentication 类并包含两个方法:authenticateget_token

from masonite.api.authentication import BaseAuthentication

class JWTAuthentication(BaseAuthentication):

    def authenticate(self, request: Request):
        """Authenticate using a JWT token
        """

        pass

    def get_token(self):
        """Returns the decrypted string as a dictionary. This method needs to be overwritten on each authentication class.

        Returns:
            dict -- Should always return a dictionary
        """

        pass

验证

此方法通过容器解析。请务必注意,如果身份验证成功,则不应返回任何内容。Masonite 将只查找在此方法中抛出的异常,然后将错误响应关联到该方法。

例如,如果我们想返回一个因找不到令牌而引发的错误,则可以引发该异常:

...
from masonite.api.exceptions import NoApiTokenFound
...

    def authenticate(self, request: Request):
        """Authenticate using a JWT token
        """

        if not request.input('token'):
            raise NoApiTokenFound

这将返回一个错误响应:

{
    "error": "no API token found"
}

获取令牌

此方法用于返回一个字典,它是令牌的解密版本。因此,无论您的身份验证类应该如何解密令牌,它都需要在此方法中这样做。这完全取决于令牌最初是如何加密的。这可能类似于:

from masonite.api.exceptions import InvalidToken
...
        def get_token(self):
        """Returns the decrypted string as a dictionary. This method needs to be overwritten on each authentication class.

        Returns:
            dict -- Should always return a dictionary
        """
        try:
            return jwt.decode(self.fetch_token(), KEY, algorithms=['HS256'])
        except jwt.DecodeError:
            raise InvalidToken

创建序列化器

序列化器是具有单个 serialize 方法的简单类。serialize 方法接受一个参数,该参数是 create、index、show、update、delete 方法之一返回的响应。

例如,如果我们返回类似模型实例的内容:

def index(self):
    return self.model.find(1)

我们将把此输出接收到序列化方法中:

def serialize(self, response):
    response # == self.model.find(1)

然后我们可以按需要序列化并返回它。以下是一个 JSON 序列化器的示例:

import json
from orator.support.collection import Collection
from orator import Model

class JSONSerializer:

    def serialize(self, response):
        """Serialize the model into JSON
        """

        if isinstance(response, Collection):
            return response.serialize()
        elif isinstance(response, Model):
            return response.to_dict()

        return response

请注意,我们获取响应,然后根据响应类型将该响应转换为字典。

一旦我们在此处转换为字典,JSONResponseMiddleware 将提取该字典并返回 JSON 响应。

内置方法

我们可以从资源中的任何位置访问一些内置方法。

获取令牌

令牌可以采用几种不同的形式进入请求。它可以是 JWT 令牌,常规令牌或其他某种形式的令牌。无论哪种方式,都需要是 「非加密」的才能使用它,并且要由认证类负责对其进行解密,因此,获得令牌是认证类的责任。

def index(self):
    token = self.get_token()

此方法只有从诸如 JWTAuthentication 这样的类中继承时才可用。该类需要包含此方法并且应返回令牌的未加密版本。

获取令牌

令牌仅可以出现在少数几个位置。两个主要位置:位于查询字符串本身(/end?token=123) 或标头 ( HTTP_AUTHORIZATION 标头) 中。我们可以使用 fetch_token() 方法获取令牌,而不管它在哪个位置:

def index(self):
    token = self.fetch_token()

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/masonite/2.3/of...

译文地址:https://learnku.com/docs/masonite/2.3/of...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~