如何使用JSON Web Token保护Flask REST API?
让我们学习如何使用JSON Web令牌来保护REST API,以防止用户和第三方应用程序滥用。
我们将使用SQLite构建一个数据库服务,并允许用户通过REST API使用POST和PUT等HTTP方法访问它。
此外,我们将了解为什么JSON Web令牌是保护REST API的合适方式,而不是摘要和基本身份验证。在我们继续之前,让我们了解JSON Web令牌、REST API和Flask框架的含义。
JSON Web令牌
JSON Web令牌,也称为JWT,是在两个方之间传输随机令牌的安全方式。JSON通常由以下三部分组成。
- 负载
- 头部
- 签名
在传输数据或信息时,JSON使用两种类型的结构形式。
- 序列化
- 反序列化
序列化形式用于通过每个请求和响应将数据传输到网络,而反序列化形式用于读取和写入数据到Web令牌。
在序列化形式中,有三个组件。
- 头部
- 负载
- 签名
头部组件定义了关于令牌的加密信息。例如:
- 是否签名或未签名JWT?
- 定义算法技术
与序列化形式不同,反序列化形式包含两个组件。
- 负载
- 头部
REST API
API(应用程序编程接口)允许两个应用程序之间进行通信以检索或提交数据。有两种常见类型的API——Web API和系统API。
在本文中,我们只查看Web API。有两种类型的Web API。
- 请求-响应API: Rest, GraphQL, 远程过程调用(RPC)
- 事件驱动API: WebHooks, WebSockets, HTTP流
REST API属于请求-响应类别。它使用GET、POST和PUT等HTTP方法来执行API操作。
一个典型的例子是当用户发送GET方法到Web服务以请求或检索特定资源或一组资源时。然后,服务器将特定的资源或一组资源发送回请求它的用户。
Flask框架
Flask是一个基于Python的框架。它是Python开发者用来构建REST API的微框架。它被称为微框架,因为它允许开发者根据自己的偏好添加自定义身份验证和任何其他后端系统。
让我们开始实施吧。我的系统设置如下。
- Ubuntu作为操作系统
- Python 2.7+
- Postman
使用virtualenv设置虚拟环境
我们需要设置一个虚拟环境,以确保某些软件包不会与系统软件包发生冲突。让我们使用virtualenv
来设置一个新的虚拟环境。
假设您的系统上有pip
命令可用,通过pip
运行以下命令来安装。
pip install virtualenv
如果您的机器上没有pip,请按照此链接_3_上的说明在您的系统上安装pip。
接下来,让我们创建一个目录来存储或保存我们的虚拟环境。使用下面显示的mkdir
命令创建一个目录
mkdir flaskproject
使用以下命令切换到flaskproject
目录
cd flaskproject
在flaskproject
目录中,使用virtualenv
工具创建一个虚拟环境,如下所示:
virtualenv flaskapi
在使用virtualenv工具创建虚拟环境后,运行cd命令进入flaskapi目录作为虚拟环境,并使用以下命令激活它。
source bin/activate
在虚拟环境中执行与该项目相关的所有任务。
使用pip安装包
现在是时候安装一些包,如flask框架和PyJWT,我们将使用它们来构建REST API和其他API项目所需的包。
创建一个名为requirements.txt的文件,其中包含以下包。
Flask
datetime
uuid
Flask-SQLAlchemy
PyJWT
使用pip安装它们。
pip install -r requirements.txt
设置数据库
让我们安装SQLite。
apt-get install sqlite3
创建一个名为library的数据库。在这个数据库中,我们将创建两个表,即Users表和Authors表。
Users表将包含注册用户。只有注册用户才能访问Authors表。
Authors表将存储已注册用户提交的作者信息或详细信息,例如作者的姓名、出生国家等。
使用以下命令创建数据库:
sqlite3 library.db
您可以使用以下命令检查是否成功创建了数据库:
.databases
打开一个新终端并在之前创建的虚拟环境中执行以下操作。
touch app.py
将以下代码粘贴到名为app.py的文件中。
from flask import Flask, request, jsonify, make_response
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
import uuid
import jwt
import datetime
from functools import wraps
上面代码中的第一行导入了诸如request和jsonify之类的包。我们将使用request在请求期间跟踪请求级别的数据,并使用jsonify以JSON格式输出响应。
接下来一行,我们从flask_sqlalchemy中导入SQLAlchemy,以便将SQLAlchemy功能集成到flask中。
从werkzeug.security中,我们导入generate_password_hash用于为用户生成密码哈希,以及check_password_hash用于在比较用户提交的密码与存储在数据库中的用户密码时检查用户的密码。
最后,我们导入uuid,也称为通用唯一标识符,用于为用户生成随机ID号码。
而在app.py文件内,使用以下代码实现library API的配置设置。
将以下代码放在导入语句之下。
app = Flask(__name__)
app.config['SECRET_KEY']='Th1s1ss3cr3t'
app.config['SQLALCHEMY_DATABASE_URI']='sqlite://///home/michael/geekdemos/geekapp/library.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
现在,根据以下示例为Users和Authors表创建两个模型。将代码复制并粘贴到app.py文件中。
将代码放在此数据库设置 db = SQLAlchemy(app)
的下方。
类Users(db.Model):
id = db.Column(db.Integer,primary_key = True)
public_id = db.Column(db.Integer)
name = db.Column(db.String(50))
password = db.Column(db.String(50))
admin = db.Column(db.Boolean)
类Authors(db.Model):
id = db.Column(db.Integer,primary_key = True)
name = db.Column(db.String(50),unique = True,nullable = False)
book = db.Column(db.String(20),unique = True,nullable = False)
country = db.Column(db.String(50),nullable = False)
booker_prize = db.Column(db.Boolean)
生成用户和作者表
在终端上,在虚拟环境中键入以下代码以生成或创建Users表和Authors表,如下所示
from app import db
db.create_all()
之后,打开虚拟环境中的 app.py
文件并创建另一个功能。
此函数将生成令牌,以便仅允许注册用户访问和执行一组针对Authors表的API操作。
将此代码放在作者表的之后
def token_required(f):
@wraps(f)
def decorator(* args,** kwargs):
令牌=无
if'request.headers中的'x-access-tokens':
令牌=请求.headers ['x-access-tokens']
if not token:
return jsonify({'message':'缺少有效令牌'})
尝试:
data = jwt.decode(token,app.config [SECRET_KEY])
current_user = Users.query.filter_by(public_id = data ['public_id']).first()
except:
return jsonify({'message':'令牌无效'})
return f(current_user,* args,** kwargs)
return装饰器
为用户表创建路由
现在,让我们创建一个路由,允许用户通过用户名和密码注册Authors API,如下所示。
再次在虚拟环境中打开 app.py
文件,并在 token_required(f)
函数下面粘贴以下代码
@app.route('/ register',methods = ['GET','POST'])
def signup_user():
data = request.get_json()
hashed_password = generate_password_hash(data ['password'],method ='sha256')
new_user = Users(public_id = str(uuid.uuid4()),name = data ['name'],password = hashed_password,admin = False)
db.session.add(new_user)
db.session.commit()
return jsonify({'message':'注册成功'})
在虚拟环境中,创建另一个路由,在 app.py
文件中允许注册用户登录。
当用户登录时,会为用户生成一个随机令牌以访问库API。
将下面的代码粘贴在我们创建的前一个路由之下。
@app.route('/ login',methods = ['GET','POST'])
def login_user():
auth =请求授权
if not auth or not auth.username or not auth.password:
return make_response('无法验证',401,{'WWW.Authentication':'基本领域:“需要登录”'})
user = Users.query.filter_by(name = auth.username).first()
if check_password_hash(user.password,auth.password):
token = jwt.encode({'public_id':user.public_id,'exp':datetime.datetime.utcnow()+ datetime.timedelta(minutes = 30)},app.config ['SECRET_KEY'])
return jsonify({'token':token.decode('UTF-8')})
return make_response('无法验证',401,{'WWW.Authentication':'基本领域:“需要登录”'})
仍然在虚拟环境中,在 app.py
文件中创建另一个路由以获取或检索所有已注册用户。
此代码检查Users表中的所有注册用户,并以JSON格式返回最终结果。
将以下代码粘贴到登录路线下面
@app.route('/users', methods=['GET'])
def get_all_users():
users = Users.query.all()
result = []
for user in users:
user_data = {}
user_data['public_id'] = user.public_id
user_data['name'] = user.name
user_data['password'] = user.password
user_data['admin'] = user.admin
result.append(user_data)
return jsonify({'users': result})
为作者表创建路线
让我们为作家表创建路线,以允许用户检索数据库中的所有作者,并删除作者。
只有具有有效令牌的用户才能执行这些API操作。
在app.py文件中,为注册用户创建一个路线来创建新的作者。
将此代码粘贴到允许用户检索所有注册用户的路线之后。
@app.route('/author', methods=['POST', 'GET'])
@token_required
def create_author(current_user):
data = request.get_json()
new_authors = Authors(name=data['name'], country=data['country'], book=data['book'], booker_prize=True, user_id=current_user.id)
db.session.add(new_authors)
db.session.commit()
return jsonify({'message' : 'new author created'})
接下来,创建另一个路线,允许具有有效令牌的注册用户检索作者表中的所有作者,如下所示。
将此代码粘贴到允许用户创建新作者的路线之后。
@app.route('/authors', methods=['POST', 'GET']) @token_required def get_authors(current_user): authors = Authors.query.filter_by(user_id=current_user.id).all() output = [] for author in authors: author_data = {} author_data['name'] = author.name author_data['book'] = author.book author_data['country'] = author.country author_data['booker_prize'] = author.booker_prize output.append(author_data) return jsonify({'list_of_authors' : output})
最后,在app.py
文件中创建一个路线来删除指定的作者,如下所示。
将此代码粘贴到允许用户检索作者列表的路线之后。
@app.route('/authors/', methods=['DELETE'])
@token_required
def delete_author(current_user, author_id):
author = Author.query.filter_by(id=author_id, user_id=current_user.id).first()
if not author:
return jsonify({'message': 'author does not exist'})
db.session.delete(author)
db.session.commit()
return jsonify({'message': 'Author deleted'})
if __name__ == '__main__':
app.run(debug=True)
完成后,保存并关闭虚拟环境中的app.py文件。
使用Postman测试库API
在本节中,我们将使用Postman工具向数据库服务发送请求。如果您的计算机上没有Postman,可以找到如何下载和安装它的方法 here。
除了Postman,我们还可以使用其他工具,如 Curl 来向服务器发送请求。
打开一个新的终端窗口,输入以下命令:
postman
命令postman
会导致您的Web浏览器显示以下页面:
您可以选择注册并创建一个免费帐户,但我们将跳过并直接访问应用程序,以便测试库API,如下所示:
在本节中,我们将允许用户通过使用POST方法以JSON格式提供用户名和唯一密码来注册库API。使用以下步骤:
- 点击标有“Body”的选项卡
- 然后选择原始按钮并选择JSON格式
- 输入用户名和密码以注册,如屏幕截图所示
- 在发送按钮旁边插入以下URL:http://127.0.0.1/register
- 最后,将方法更改为POST并按下发送按钮。
它将显示如下所示的输出:
现在我们已经成功注册了一个用户。让我们继续允许刚注册的用户登录,以生成一个临时随机令牌,以便使用以下步骤访问”Authors”表:
- 点击授权选项卡。
- 在类型部分下选择基本身份验证。
- 然后使用之前注册的用户名和密码填写用户名和密码表单。
- 最后,按下发送按钮以登录并生成一个随机令牌。
用户登录成功后,将为用户生成一个随机令牌,如屏幕截图所示。
我们将使用生成的随机令牌来访问”Authors”表。
在本节中,我们将使用POST方法向”Authors”表添加作者信息,具体步骤如下:
- 点击标有“Headers”的选项卡
- 包含屏幕截图中显示的以下HTTP标头
- 接下来,点击标有“Body”的选项卡并输入新作者的详细信息
- 然后按下发送按钮将作者的详细信息添加到”Authors”表中
您还可以通过以下方法检索”Authors”表中的作者信息:
- 确保您的生成令牌位于标头部分。如果没有,请将其填入令牌。
- 在发送按钮旁边,输入此URL:
http://127.0.0.1/authors
- 然后将HTTP方法更改为GET并按下发送按钮以检索作者详细信息。
最后,您可以使用DELETE
方法从”Authors”表中删除作者,具体步骤如下:
- 确保您的令牌仍在标头部分。您可以检查标头选项卡确保必要的信息已经设置。
- 在发送按钮旁边,输入此URL:
http://127.0.0.1/sam
- 然后按下发送按钮以删除指定的用户。
您可以在Github.上找到完整的源代码。您可以克隆它并在您的机器上查看。