使用 VueJS、Flask 和 RethinkDB 实现简单文件存储服务的用户模型和身份验证控制器
入门
有关如何设置工作区的更多信息,请查看本系列的第一个指南:使用 VueJS、Flask 和 RethinkDB 构建简单文件存储服务的简介和设置。
用户模型
我们将为我们的模型添加代码。对于此应用程序,我们只需要两个模型即可开始。对于此步骤,我们将仅创建用户模型。
我们首先连接到 RethinkDB 并创建一个到我们数据库的连接对象。
import rethinkdb as r
from flask import current_app
conn = r.connect(db="papers")
class RethinkDBModel(object):
pass
我们从应用程序配置中引用了数据库名称。在 flask 中,我们有current_app变量,它保存对当前正在运行的应用程序实例的引用。
我为什么要创建一个空白的RethinkDBModel类?因为我们可能想要在模型类之间共享一些东西;如果需要跨模型共享,则可以使用这个类。
我们的User类将继承自这个空基类。在User中,我们将有几个函数用于从控制器与数据库进行交互。
我们从create()函数开始。当我们需要在表中创建用户文档时,将调用此函数。
class User(RethinkDBModel):
_table = 'users'
@classmethod
def create(cls, **kwargs):
fullname = kwargs.get('fullname')
email = kwargs.get('email')
password = kwargs.get('password')
password_conf = kwargs.get('password_conf')
if password != password_conf:
raise ValidationError("Password and Confirm password need to be the same value")
password = cls.hash_password(password)
doc = {
'fullname': fullname,
'email': email,
'password': password,
'date_created': datetime.now(r.make_timezone('+01:00')),
'date_modified': datetime.now(r.make_timezone('+01:00'))
}
r.table(cls._table).insert(doc).run(conn)
这里,我们使用了classmethod装饰器。此装饰器使我们能够从方法主体内访问类实例。我们将使用类实例从方法内访问_table属性。_table存储该模型的表名。
我们还在此处添加了代码以确保密码和password_conf字段相同。发生这种情况时,将抛出ValidationError。异常将存储在/api/utils/errors.py模块中。下面是ValidationError的定义:
class ValidationError(Exception):
pass
我们使用命名异常,因为它们更容易追踪。
注意我们在这里是如何使用datetime.now(r.make_timezone('+01:00')) 的?当我使用没有时区的datetime.now()时,我遇到了一些问题。RethinkDB要求在文档中的日期字段上设置时区信息。除非我们将其指定为now()函数的参数(有关更多信息,请参阅此处) ,否则 Python 函数默认不会为我们提供此功能。使用r.make_timezone('+01:00'),我们可以创建一个可用于datetime.now()函数的时区对象。
如果一切顺利,没有遇到任何异常,我们将对r.table(table_name)返回的表对象调用insert()方法。此方法采用包含数据的字典。此数据将作为新文档存储在所选表中。
我们在类中调用了hash_password()方法。此方法利用passlib包中的hash.pbkdf2_sha256模块对密码进行相当安全的哈希处理。除此之外,我们还需要创建一种验证密码的方法。
from passlib.hash import pbkdf2_sha256
class User(RethinkDBModel):
_table = 'users'
@classmethod
def create(cls, **kwargs):
fullname = kwargs.get('fullname')
email = kwargs.get('email')
password = kwargs.get('password')
password_conf = kwargs.get('password_conf')
if password != password_conf:
raise ValidationError("Password and Confirm password need to be the same value")
password = cls.hash_password(password)
doc = {
'fullname': fullname,
'email': email,
'password': password,
'date_created': datetime.now(r.make_timezone('+01:00')),
'date_modified': datetime.now(r.make_timezone('+01:00'))
}
r.table(cls._table).insert(doc).run(conn)
@staticmethod
def hash_password(password):
return pbkdf2_sha256.encrypt(password, rounds=200000, salt_size=16)
@staticmethod
def verify_password(password, _hash):
return pbkdf2_sha256.verify(password, _hash)
使用密码以及rounds和salt_size 的值调用pbkdf2_sha256.encrypt()方法。有关如何自定义加密以及库如何工作的详细信息,请参阅此处。仅提供一些关于决定使用PBKDF2 的背景信息:
从安全角度来看,PBKDF2 目前是领先的密钥派生函数之一,并且没有已知的安全问题。
--摘自 passlib 文档
verify_password方法将使用密码字符串和哈希值进行调用。如果密码有效,它将返回 true 或 false。
现在我们转到validate()函数。此函数将在登录方法中使用电子邮件地址和密码进行调用。该函数将使用电子邮件字段作为索引检查文档是否存在,然后将密码哈希与提供的密码进行比较。
除此之外,由于我们将使用 JWT(JSON Web Token)进行基于令牌的身份验证,因此如果用户提供有效信息,我们将生成一个令牌。当我们添加完逻辑后,整个models.py将如下所示。
import os
import rethinkdb as r
from jose import jwt
from datetime import datetime
from passlib.hash import pbkdf2_sha256
from flask import current_app
from api.utils.errors import ValidationError
conn = r.connect(db="papers")
class RethinkDBModel(object):
pass
class User(RethinkDBModel):
_table = 'users'
@classmethod
def create(cls, **kwargs):
fullname = kwargs.get('fullname')
email = kwargs.get('email')
password = kwargs.get('password')
password_conf = kwargs.get('password_conf')
if password != password_conf:
raise ValidationError("Password and Confirm password need to be the same value")
password = cls.hash_password(password)
doc = {
'fullname': fullname,
'email': email,
'password': password,
'date_created': datetime.now(r.make_timezone('+01:00')),
'date_modified': datetime.now(r.make_timezone('+01:00'))
}
r.table(cls._table).insert(doc).run(conn)
@classmethod
def validate(cls, email, password):
docs = list(r.table(cls._table).filter({'email': email}).run(conn))
if not len(docs):
raise ValidationError("Could not find the e-mail address you specified")
_hash = docs[0]['password']
if cls.verify_password(password, _hash):
try:
token = jwt.encode({'id': docs[0]['id']}, current_app.config['SECRET_KEY'], algorithm='HS256')
return token
except JWTError:
raise ValidationError("There was a problem while trying to create a JWT token.")
else:
raise ValidationError("The password you inputted was incorrect.")
@staticmethod
def hash_password(password):
return pbkdf2_sha256.encrypt(password, rounds=200000, salt_size=16)
@staticmethod
def verify_password(password, _hash):
return pbkdf2_sha256.verify(password, _hash)
在validate()方法中需要注意几点。首先,这里在表对象上使用filter()函数。此命令接受用于搜索表的字典。在某些情况下,此函数还可以接受谓词。此谓词可以是lambda函数,其使用方式与python filter函数或任何其他以函数为参数的函数类似。该函数返回一个游标,可用于访问查询返回的所有文档。游标是可迭代的,因此我们可以使用for ... in循环迭代游标对象。在本例中,我们选择使用Python list函数将可迭代对象转换为列表。
正如您所期望的,我们基本上在这里做两件事。我们想知道电子邮件地址是否存在,然后密码是否正确。对于第一部分,我们基本上计算集合。如果为空,我们会引发错误。对于第二部分,我们调用 verify_password ()函数将提供的密码与数据库中的哈希进行比较。如果它们不匹配,我们会引发异常。
同样值得注意的是我们如何使用jwt.encode()创建 JWT 令牌并将其返回给控制器。此方法相当简单,您可以在此处查看文档。
模型就是这样。让我们继续讨论控制器。在这个模型中,我们试图遵循“胖模型”和“瘦控制器”的原则。大多数逻辑都在模型中。这样,我们的控制器只关注向 API 最终用户进行路由和错误报告。
身份验证控制器
对于身份验证控制器,我们需要添加 Flask RESTful 资源子类。Django Web 开发类似于基于类的视图。它只是作为flask_restful.Resource类的子类创建的。您的子类将具有映射到相应 HTTP 动词的方法。例如,如果我们想要实现 GET 操作,我们将在 Resource 子类中创建一个get()方法。该过程通过使用api.add_resource()方法将 URL 映射到相应的类来完成。
现在让我们添加两个类;一个负责处理登录路由的 POST 操作,另一个负责处理注册路由的 POST 操作。
我们首先创建所需的类。这些类应存储在/api/controllers/auth.py文件中。
from flask_restful import Resource
class AuthLogin(Resource):
def post(self):
pass
class AuthRegister(Resource):
def post(self):
pass
接下来,我们将创建这些路由并在我们的/api/__init__.py文件中引用相应的类。
from flask import Flask, Blueprint
from flask_restful import Api
from api.controllers import auth
from config import config
def create_app(env):
app = Flask(__name__)
app.config.from_object(config[env])
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
api.add_resource(auth.AuthLogin, '/auth/login')
api.add_resource(auth.AuthRegister, '/auth/register')
app.register_blueprint(api_bp, url_prefix="/api/v1")
return app
现在让我们回到控制器文件来添加一些逻辑。这里所需的逻辑与你对一般身份验证系统所做的逻辑类似。
from flask_restful import reqparse, abort, Resource
from api.models import User
from api.utils.errors import ValidationError
class AuthLogin(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('email', type=str, help='You need to enter your e-mail address', required=True)
parser.add_argument('password', type=str, help='You need to enter your password', required=True)
args = parser.parse_args()
email = args.get('email')
password = args.get('password')
try:
token = User.validate(email, password)
return {'token': token}
except ValidationError as e:
abort(400, message='There was an error while trying to log you in -> {}'.format(e.message))
class AuthRegister(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('fullname', type=str, help='You need to enter your full name', required=True)
parser.add_argument('email', type=str, help='You need to enter your e-mail address', required=True)
parser.add_argument('password', type=str, help='You need to enter your chosen password', required=True)
parser.add_argument('password_conf', type=str, help='You need to enter the confirm password field', required=True)
args = parser.parse_args()
email = args.get('email')
password = args.get('password')
password_conf = args.get('password_conf')
fullname = args.get('fullname')
try:
User.create(
email=email,
password=password,
password_conf=password_conf,
fullname=fullname
)
return {'message': 'Successfully created your account.'}
except ValidationError as e:
abort(400, message='There was an error while trying to create your account -> {}'.format(e.message))
如前所述,大部分逻辑和数据库交互都已推送到模型。控制器逻辑相对简单。
总结一下,对于登录控制器AuthLogin,我们创建了一个post()函数,该函数接受电子邮件地址和密码,使用reqparse验证字段,并调用User.validate()来验证发送的信息并返回一个令牌。如果发生错误,我们会捕获它并返回错误消息。
类似地,对于AuthRegister<
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~