Masonite API
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
}
您可能不想显示所有模型属性,如 id
、email
或 password
。我们可以选择使用 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 类并包含两个方法:authenticate
和 get_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()
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。