Flask Rest API -第三部分-身份验证和授权
你好!在本系列的上一篇中,我们学习了如何使用Flask REST API,Blueprint
以及Flask-Restful
如何以更易于维护的方式构建它。
目前,任何人都可以读取、添加、删除和更新我们应用中的电影。现在,让我们学习如何限制任何不受信任的人员(Authentication
)创建电影。此外,我们还将学习如何实现Authorization
,以便只有在应用中添加电影的人员才能删除/修改它。
要实现这些功能,首先我们必须创建一个新的文档模型来存储用户信息。那就让我们开始吧。
与创建Movie
文档模型类似,我们将创建一个User
文档模型。在文档模型后添加以下代码Movie
。
#~/movie-bag/database/models.py
...
class User(db.Document):
email = db.EmailField(required=True, unique=True)
password = db.StringField(required=True, min_length=6)
在这里,我们创建了这个,以便当用户注册时,会创建一个包含字段email
和的新用户文档password
。
但是将密码明文保存StringField
是个糟糕的主意。如果有人访问了你的数据库,所有用户的密码都会暴露。为了防止这种情况发生,我们会将hash
密码保存为某种cryptic
格式,这样就没有人能轻易地找到真正的密码了。
为了对密码进行哈希处理,我们将使用一个流行的哈希函数bcrypt
。你可能已经猜到了,我们将使用一个名为flask-bcrypt的 Flask 扩展来实现这一点。
让我们安装吧flask-bcrypt
。
pipenv install flask-bcrypt
让我们在我们的中初始化 flask-bcrypt app.py
。
#~/movie-bag/app.py
from flask import Flask
+from flask_bcrypt import Bcrypt
from database.db import initialize_db
from flask_restful import Api
from resources.routes import initialize_routes
app = Flask(__name__)
api = Api(app)
+bcrypt = Bcrypt(app)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
现在我们将创建两种方法:一种用于创建密码哈希generate_password_hash()
,另一种用于检查用户登录使用的密码是否生成与数据库中保存的密码相同的哈希check_password_hash()
。
让我们更新一下,models.py
使其看起来像这样:
#~/movie-bag/database/models.py
from .db import db
+from flask_bcrypt import generate_password_hash, check_password_hash
...
class User(db.Document):
email = db.EmailField(required=True, unique=True)
password = db.StringField(required=True, min_length=6)
+
+ def hash_password(self):
+ self.password = generate_password_hash(self.password).decode('utf8')
+
+ def check_password(self, password):
+ return check_password_hash(self.password, password)
现在让我们为创建一个 API 端点signup
。使用以下代码添加到文件夹auth.py
内。resources
#~/movie-bag/resources/auth.py
from flask import request
from database.models import User
from flask_restful import Resource
class SignupApi(Resource):
def post(self):
body = request.get_json()
user = User(**body)
user.hash_password()
user.save()
id = user.id
return {'id': str(id)}, 200
该端点使用用户发送的对象创建用户文档email
并password
接收该对象。JSON
让我们在我们的中注册这个端点routes.py
。
from .movie import MoviesApi, MovieApi
+from .auth import SignupApi
def initialize_routes(api):
api.add_resource(MoviesApi, '/api/movies')
api.add_resource(MovieApi, '/api/movies/<id>')
+
+ api.add_resource(SignupApi, '/api/auth/signup')
让我们测试一下用户注册。将正文JSON
发送email
至password
http://localhost:5000/api/auth/signup
如果我们查看数据库,我们可以看到,与我们在 API 请求中发送的密码相比,我们的密码被散列为一些随机密码。
注意:为了查看存储在数据库中的信息,我使用了mongo compass
好的,我们已经创建了通过创建用户的功能signup
,现在我们需要能够login
以该用户的身份。
为了帮助用户登录网站,我们需要提供验证用户身份的功能。这样一来,用户每次需要在网站上执行操作时都可以发送email
和password
,这从安全角度来看并非良策。因此,我们需要提供这样的功能:用户登录网站后,可以使用令牌访问网站的其他部分。
有很多方法可以使用基于令牌的身份验证,在本部分中,我们将学习JWT
也称为JSON Web 令牌。
要使用 JWT,让我们安装另一个名为flask-jwt-extended的 flask 扩展,它使用我们想要保存为令牌的值(在我们的例子中是userid
)并将其与salt
(密钥)结合起来创建一个令牌。
pipenv instll flask-jwt-extended
由于secret-key
我们用来创建 JWT 的内容需要保存在代码库的其他地方,因此我们将使用.env
文件来保存机密,并.env
使用环境变量将文件的位置提供给我们的应用程序。
为此,我们在文件夹.env
中创建一个新文件movie-bag
并向其中添加以下内容。
JWT_SECRET_KEY = 't1NP63m4wnBg6nyHYKfmc2TpCOGI4nss'
的值JWT_SECRET_KEY
可以是任意值,但不能使其更难猜测。
让我们更新我们的app.py
以使用.env
文件中的配置并初始化JWT
。
#~/movie-bag/app.py
from flask import Flask
from flask_bcrypt import Bcrypt
+from flask_jwt_extended import JWTManager
+
from database.db import initialize_db
from flask_restful import Api
from resources.routes import initialize_routes
app = Flask(__name__)
+app.config.from_envvar('ENV_FILE_LOCATION')
+
api = Api(app)
bcrypt = Bcrypt(app)
+jwt = JWTManager(app)
app.config['MONGODB_SETTINGS'] = {
'host': 'mongodb://localhost/movie-bag'
这是环境变量,它应该存储相对于文件ENV_FILE_LOCATION
的位置.env
app.py
要设置此值 mac/linux 可以运行以下命令:
export ENV_FILE_LOCATION=./.env
Windows 用户可以运行以下命令:
set ENV_FILE_LOCATION=./.env
现在,我们终于可以实现login
端点了。让我们更新文件夹auth.py
内部resources
:
-from flask import request
+from flask import Response, request
+from flask_jwt_extended import create_access_token
from database.models import User
from flask_restful import Resource
+import datetime
+
class SignupApi(Resource):
def post(self):
body = request.get_json()
@@ -9,4 +11,16 @@ class SignupApi(Resource):
user.hash_password()
user.save()
id = user.id
return {'id': str(id)}, 200
+
+class LoginApi(Resource):
+ def post(self):
+ body = request.get_json()
+ user = User.objects.get(email=body.get('email'))
+ authorized = user.check_password(body.get('password'))
+ if not authorized:
+ return {'error': 'Email or password invalid'}, 401
+
+ expires = datetime.timedelta(days=7)
+ access_token = create_access_token(identity=str(user.id), expires_delta=expires)
+ return {'token': access_token}, 200
这里我们通过给定的邮箱地址搜索用户,并检查发送的密码是否与数据库中保存的哈希密码相同。
如果密码和邮箱地址正确,我们将创建访问令牌,并将create_access_token()
其用作user.id
标识符。该令牌的有效期7 days.
为 7 天,这意味着用户在 7 天后将无法使用此令牌访问网站。
让我们在我们的routes.py
from .movie import MoviesApi, MovieApi
-from .auth import SignupApi
+from .auth import SignupApi, LoginApi
def initialize_routes(api):
api.add_resource(MoviesApi, '/api/movies')
api.add_resource(MovieApi, '/api/movies/<id>')
api.add_resource(SignupApi, '/api/auth/signup')
+ api.add_resource(LoginApi, '/api/auth/login')
现在,我们需要限制未经授权的用户在我们的应用程序中添加、编辑和删除电影。为此,我们需要@jwt_required
在端点中添加装饰器。这可以保护我们的端点免受无效或过期 JWT 的影响。
更新movie.py
为:
#~/movie-bag/resources/movie.py
from flask import Response, request
+from flask_jwt_extended import jwt_required
from database.models import Movie
from flask_restful import Resource
class MoviesApi(Resource):
...
+
+ @jwt_required
def post(self):
body = request.get_json()
movie = Movie(**body).save()
...
class MovieApi(Resource):
+ @jwt_required
def put(self, id):
body = request.get_json()
Movie.objects.get(id=id).update(**body)
return '', 200
+
+ @jwt_required
def delete(self, id):
movie = Movie.objects.get(id=id).delete()
return '', 200
现在让我们测试一下。
首先,我们必须以之前创建的用户身份登录signup
。
我们从服务器获取了令牌,现在尝试从 API 端点 创建一个影片http://localhost:5000/api/movies
。正如你所见,由于 API 端点受 保护,因此无法执行此操作并会报错jwt
。
注意:我们将在本系列的后续部分学习如何使错误消息更友好。
login
现在让我们在标题中使用我们之前获得的令牌Authorization
。
要在 Postman 中使用授权标头,请按照以下步骤操作:
1)转到Authorization
选项卡。2
)选择Bearer Token
表单TYPE
下拉菜单。3
)粘贴您之前从 4)获得的令牌/login
,最后发送请求。
让我们添加一项功能,以便只有创建电影的用户才能删除或编辑电影。
让我们更新models.py
并创建用户和电影之间的关系。
#~/movie-bag/database/models.py
class Movie(db.Document):
name = db.StringField(required=True, unique=True)
casts = db.ListField(db.StringField(), required=True)
genres = db.ListField(db.StringField(), required=True)
+ added_by = db.ReferenceField('User')
class User(db.Document):
email = db.EmailField(required=True, unique=True)
password = db.StringField(required=True, min_length=6)
+ movies = db.ListField(db.ReferenceField('Movie', reverse_delete_rule=db.PULL))
+
+User.register_delete_rule(Movie, 'added_by', db.CASCADE)
user
我们在和之间创建了一对多关系movie
。这意味着一个用户可以拥有一部或多部电影,并且一部电影只能由一个用户创建。此处reverse_delete_rule
的电影字段User
表示如果删除了该电影,则应从用户文档中提取该电影。
同样,User.register_delete_rule(Movie, 'added_by', db.CASCADE)
创建了另一条删除规则,这意味着如果删除了用户,则该用户创建的电影也会被删除。
注意:我必须added_by
单独注册 的删除规则,因为User
在定义 时尚未定义Movie
现在,让我们更新movie.py
以应用授权。
from flask import Response, request
-from flask_jwt_extended import jwt_required
-from database.models import Movie
+from database.models import Movie, User
+from flask_jwt_extended import jwt_required, get_jwt_identity
from flask_restful import Resource
class MoviesApi(Resource):
def get(self):
movies = Movie.objects().to_json()
return Response(movies, mimetype="application/json", status=200)
@jwt_required
def post(self):
+ user_id = get_jwt_identity()
body = request.get_json()
- movie = Movie(**body).save()
+ user = User.objects.get(id=user_id)
+ movie = Movie(**body, added_by=user)
+ movie.save()
+ user.update(push__movies=movie)
+ user.save()
id = movie.id
return {'id': str(id)}, 200
class MovieApi(Resource):
@jwt_required
def put(self, id):
+ user_id = get_jwt_identity()
+ movie = Movie.objects.get(id=id, added_by=user_id)
body = request.get_json()
Movie.objects.get(id=id).update(**body)
return '', 200
@jwt_required
def delete(self, id):
- movie = Movie.objects.get(id=id).delete()
+ user_id = get_jwt_identity()
+ movie = Movie.objects.get(id=id, added_by=user_id)
+ movie.delete()
return '', 200
def get(self, id):
...
这里的get_jwt_identity()
方法返回的值create_access_token()
在我们的例子中是user.id
。因此,我们只删除/更新由向应用程序发送请求的用户添加的电影。
您可以在此处找到此部分的完整代码
我们从本系列的这一部分中学到了什么?
- 如何使用哈希用户密码
flask-bcrypt
- 如何使用创建 JSON 令牌
flask-jwt-extended
- 如何保护 API 端点免受未经授权的访问。
- 如何实现授权,以便只有添加电影的用户才能删除/更新电影。
由于我们的应用程序中存在许多不友好的错误和异常,因此在下一部分中我们将学习如何处理 REST API 中的错误和异常。
如果您遇到任何困难,请告诉我,以便我为您提供指导。另外,如果您希望我在下一部分/系列中介绍某些内容,请不要忘记在下方提及。
祝您编码愉快😊
文章来源:https://dev.to/paurakhsharma/flask-rest-api-part-3-authentication-and-authorization-5935