让我们学习如何使用JSON网络令牌保护REST API, 以防止用户和第三方应用滥用它。
我们将使用SQLite构建数据库服务, 并允许用户使用HTTP方法(例如POST和PUT)通过REST API来访问数据库服务。
此外, 我们将了解为什么JSON Web Token是保护其余API而不是摘要和基本身份验证的合适方法。在继续之前, 让我们了解术语JSON网络令牌, REST API和Flask框架。
JSON Web Token
JSON Web Token(也称为JWT)是在两方或实体之间传输随机令牌的安全方法。 JSON通常由以下三部分组成。
- 有效载荷Payload
- 标头Header
- 签名Signature
在双方之间传输数据或信息时, JSON使用两种类型的结构形式。
- 序列化
- 反序列化
通过每个请求和响应将数据传输到网络时, 将使用序列化形式, 而在将数据写入Web Token和将数据写入Web Token时, 将使用反序列化形式。
在序列化形式中, 包含三个组件。
- 标头
- 有效载荷
- 签名
标头组件定义有关令牌的密码信息。例如:
- 它是签名的还是未签名的JWT?
- 定义算法技术
与序列化格式不同, 反序列化格式包含两个组件。
- 有效载荷
- 标头
REST API
API(应用程序编程接口)允许两个应用程序之间的通信来检索或提交数据。有两种常见的API类型-网络和系统API。
在本文中, 我们将仅介绍Web API。 Web API有两种类型。
- 请求–响应API:Rest, GraphQL, 远程过程调用(RPC)
- 事件驱动的API:WebHooks, Web套接字, HTTP流
REST API属于请求-响应类别。它利用HTTP方法(例如GET, POST和PUT)执行API操作。
一个经典的例子是, 当用户向Web服务发送GET方法以请求或检索特定资源或资源集合时。然后, 服务器将特定资源或资源集合发回给请求它的用户。
Flask框架
Flask是基于python的框架。它是python开发人员用来构建其余API的微型框架。之所以称为微框架, 是因为它允许开发人员添加基于首选项的自定义身份验证和任何其他后端系统。
让我们从实施开始。我的系统设置如下。
- Ubuntu作为操作系统
- Python 2.7以上
- Postman
使用virtualenv设置虚拟环境
我们需要建立一个虚拟环境, 以确保某些软件包不会与系统软件包冲突。让我们使用virtualenv设置新的虚拟环境。
假设系统上有pip命令可用, 请通过pip运行以下命令进行安装。
pip install virtualenv
如果你的计算机上没有pip, 请按照本文档在系统上安装pip。
接下来, 让我们创建一个目录来存储或保存我们的虚拟环境。使用下面显示的mkdir命令创建目录
mkdir flaskproject
使用以下命令进入flaskproject目录
cd flaskproject
在flaskproject目录内, 使用virtualenv工具创建一个虚拟环境, 如下所示:
virtualenv flaskapi
使用virtualenv工具创建虚拟环境后, 运行cd命令以将目录更改为flaskapi目录作为虚拟环境, 并使用下面的命令将其激活。
source bin/activate
在虚拟环境中执行与此项目相关的所有任务。
使用pip安装软件包
现在是时候安装诸如flask框架和PyJWT之类的软件包, 我们将使用它们来构建其余的API和API项目所需的其他软件包。
使用以下程序包创建requirements.txt文件。
Flask
datetime
uuid
Flask-SQLAlchemy
PyJWT
用pip安装它们。
pip install -r requirements.txt
建立一个数据库
让我们安装SQLite。
apt-get install sqlite3
创建一个名为库的数据库。在此数据库中, 我们将创建两个表, 即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文件中, 使用以下app.py文件中的代码实现库API的配置设置。
将以下代码放在import语句下面。
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)
现在, 为”用户和作者”表创建两个模型, 如下所示。将代码复制并粘贴到app.py文件中。
将代码放在此数据库设置下方的右下方db = SQLAlchemy(app)
class 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)
class 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操作。
将此代码放在Authors表的数据库模型下
def token_required(f):
@wraps(f)
def decorator(*args, **kwargs):
token = None
if 'x-access-tokens' in request.headers:
token = request.headers['x-access-tokens']
if not token:
return jsonify({'message': 'a valid token is missing'})
try:
data = jwt.decode(token, app.config[SECRET_KEY])
current_user = Users.query.filter_by(public_id=data['public_id']).first()
except:
return jsonify({'message': 'token is invalid'})
return f(current_user, *args, **kwargs)
return decorator
为用户表创建路由
现在, 让我们创建一条路径, 以允许用户通过用户名和密码注册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': 'registered successfully'})
在虚拟环境中, 在app.py文件中创建另一个路由, 以允许注册用户登录。
用户登录时, 将生成一个随机令牌供用户访问库API。
将代码粘贴到我们创建的上一条路线下方。
@app.route('/login', methods=['GET', 'POST'])
def login_user():
auth = request.authorization
if not auth or not auth.username or not auth.password:
return make_response('could not verify', 401, {'WWW.Authentication': 'Basic realm: "login required"'})
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('could not verify', 401, {'WWW.Authentication': 'Basic realm: "login required"'})
仍然在虚拟环境中, 在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'})
接下来, 创建另一条路由, 以允许具有有效令牌的注册用户检索Authors表中的所有作者, 如下所示。
将此代码粘贴到允许用户创建新作者的路由下方。
@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/<author_id>', 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
在本节中, 我们将使用邮递员工具将请求发送到数据库服务。如果你的计算机上没有邮递员, 则可以在此处找到如何下载和安装邮递员。
除邮递员外, 我们还可以使用其他工具(例如Curl)将请求发送到服务器。
打开一个新终端, 然后键入以下内容:
postman
命令邮递员将使你的Web浏览器显示以下页面:
你可以决定注册并创建一个免费帐户, 但是我们将跳过并直接访问该应用以测试库API, 如下所示:
在本部分中, 我们将允许用户通过使用POST方法(以下步骤)以JSON格式提供用户名和唯一密码来注册库API。
- 单击标签为身体的选项卡
- 然后选择原始按钮并选择JSON格式
- 输入用户名和密码进行注册, 如屏幕截图所示
- 在发送按钮旁边, 插入以下URL http://127.0.0.1/register
- 最后, 将方法更改为POST, 然后按发送按钮。
它将显示以下输出, 如下所示:
现在我们已经成功注册了一个用户。让我们继续进行一下, 以允许刚刚注册的用户登录以生成临时随机令牌, 可以使用以下步骤访问Authors表:
- 单击授权选项卡。
- 在类型部分下, 选择基本身份验证。
- 然后用你之前注册的用户名和密码填写用户名和密码表格。
- 最后, 按发送按钮登录并生成随机令牌。
用户成功登录后, 将为用户生成一个随机令牌, 如屏幕截图所示。
我们将利用生成的随机令牌访问Authors表。
在本节中, 我们将使用以下步骤通过POST方法将作者的信息添加到Authors表中:
- 点击标题标签
- 包括屏幕快照中显示的以下HTTP标头
- 接下来, 点击”正文”标签, 然后输入新作者的详细信息
- 然后按发送按钮, 将作者的详细信息添加到作者的表中
你还可以通过以下方式在”作者”表中检索作者的信息:
- 确保你生成的令牌在标题部分中。如果不存在, 则需要用令牌填充它。
- 在发送按钮旁边, 输入此URL http://127.0.0.1/authors
- 然后将HTTP方法更改为GET, 然后按发送按钮以检索作者详细信息。
最后, 你可以按照以下步骤通过DELETE方法在Authors表中删除作者:
- 确保你的令牌仍在标题部分。你可以检查标题标签以确保必要的信息到位。
- 在发送按钮旁边, 输入此URL http://127.0.0.1/sam
- 然后按发送按钮删除你指定的用户。
你可以在Github上找到完整的源代码。你可以克隆它并在计算机上检出它。
评论前必须登录!
注册