前言
在之前我們已開發了幾個接口,並且可以正常使用,那么今天我們將繼續完善一下。我們注意到之前的接口,都是不需要進行任何驗證就可以使用的,其實我們可以使用 token
,比如設置在修改或刪除用戶信息的時候需要進行 token
登錄驗證,這個地方我們將引入 Redis
用於存儲登錄時產生的 token
。
本人環境:Python 3.7.0 、Redis 5.0.8
新增Redis配置
我們在項目根路徑下 config
包中,修改文件 setting.py
,在該文件中配置 Redis 的服務器地址、端口、密碼、token過期時間(單位:秒)等參數。
# Redis配置
REDIS_HOST = "192.168.89.128"
REDIS_PORT = 6379
REDIS_PASSWD = "123456"
# token過期時間(單位:秒)
EXPIRE_TIME = 600
Python操作Redis
- 安裝redis模塊
使用Python來操作Redis,需要用到 redis
這個第三方庫,具體安裝方法如下:
pip install redis
我這里安裝的版本是 3.4.1
。
D:\>pip3 show redis
Name: redis
Version: 3.4.1
Summary: Python client for Redis key-value store
Home-page: https://github.com/andymccurdy/redis-py
Author: Andy McCurdy
Author-email: sedrik@gmail.com
License: MIT
Location: d:\python\installation\lib\site-packages
Requires:
Required-by:
- 封裝Python操作Redis的代碼
我們在項目根路徑下 common
包中,新建文件 redis_operate.py
,該文件下簡單封裝了Python操作Redis的代碼,后續將通過調用該文件的 redis_db
對象及方法來操作Redis。
import redis
from config.setting import REDIS_HOST, REDIS_PORT, REDIS_PASSWD, EXPIRE_TIME
class RedisDb():
def __init__(self, host, port, passwd):
# 建立數據庫連接
self.r = redis.Redis(
host=host,
port=port,
password=passwd,
decode_responses=True # get() 得到字符串類型的數據
)
def handle_redis_token(self, key, value=None):
if value: # 如果value非空,那么就設置key和value,EXPIRE_TIME為過期時間
self.r.set(key, value, ex=EXPIRE_TIME)
else: # 如果value為空,那么直接通過key從redis中取值
redis_token = self.r.get(key)
return redis_token
redis_db = RedisDb(REDIS_HOST, REDIS_PORT, REDIS_PASSWD)
在Redis中,我們存儲數據是 str
字符串類型,但由於python3與redis交互的驅動問題,默認通過 get() 取出來是 bytes
字節類型,為解決這個問題,我們在上面代碼創建連接時,設置了 decode_responses=True
,這樣通過 get() 取出來就是 str 字符串類型。
登錄時設置token
一般情況,我們登錄成功之后,才會產生token,以便在請求其他接口時進行登錄驗證。因此,我們需要在返回登錄成功信息之前,設置token,並把當前登錄用戶和設置token,作為redis中的 key
和 value
,放到redis中進行存儲。修改用戶登錄接口如下:
@app.route("/login", methods=['POST'])
def user_login():
"""登錄用戶"""
username = request.values.get("username", "").strip()
password = request.values.get("password", "").strip()
if username and password: # 注意if條件中空串 "" 也是空, 按False處理
sql1 = "SELECT username FROM user WHERE username = '{}'".format(username)
res1 = db.select_db(sql1)
print("查詢到用戶名 ==>> {}".format(res1))
if not res1:
return jsonify({"code": 1003, "msg": "用戶名不存在!!!"})
sql2 = "SELECT * FROM user WHERE username = '{}' and password = '{}'".format(username, password)
res2 = db.select_db(sql2)
print("獲取 {} 用戶信息 == >> {}".format(username, res2))
if res2:
timeStamp = int(time.time()) # 獲取當前時間戳
token = "{}{}".format(username, timeStamp)
redis_db.handle_redis_token(username, token) # 把token放到redis中存儲
login_info = { # 構造一個字典,將 id/username/token/login_time 返回
"id": res2[0]["id"],
"username": username,
"token": token,
"login_time": time.strftime("%Y/%m/%d %H:%M:%S")
}
return jsonify({"code": 0, "login_info": login_info, "msg": "恭喜,登錄成功!"})
return jsonify({"code": 1002, "msg": "用戶名或密碼錯誤!!!"})
else:
return jsonify({"code": 1001, "msg": "用戶名或密碼不能為空!!!"})
在這里,我們設置的 token
是由 用戶名+當前時間戳
拼接而成的,並把其放到redis中,可以到redis中查看token,或者在登錄成功后接口返回數據中的 login_info
中查看。而 token 的有效時間,這個是在配置文件 setting.py
中設置的 EXPIRE_TIME = 600
,即 600 秒
后當前用戶的 token 才會過期失效。
刪除用戶請求接口實現
接下來,我們准備新開發個接口:刪除用戶接口。該接口需要 管理員用戶
登錄認證后才可以進行操作,管理員用戶可以刪除任何 普通用戶
信息。
原本我是想通過 DELETE 請求方式來開發該接口,但好像使用 DELETE 方式的HTTP請求中,不允許傳入Body,因此為了方便一些,我們這里仍使用 POST 方式來請求。
- 刪除用戶接口(POST請求方式)
@app.route("/delete/user/<int:id>", methods=['POST'])
def user_delete(id):
username = request.json.get("username", "").strip() # 當前登錄的管理員用戶
token = request.json.get("token", "").strip() # token口令
if username and token:
redis_token = redis_db.handle_redis_token(username) # 從redis中取token
if redis_token:
if redis_token == token: # 如果從redis中取到的token不為空,且等於請求body中的token
sql1 = "SELECT role FROM user WHERE username = '{}'".format(username)
res1 = db.select_db(sql1)
print("根據用戶名 【 {} 】 查詢到用戶類型 == >> {}".format(username, res1))
user_role = res1[0]["role"]
if user_role == 0: # 如果當前登錄用戶是管理員用戶
sql2 = "SELECT * FROM user WHERE id = '{}'".format(id)
res2 = db.select_db(sql2)
print("根據用戶ID 【 {} 】 查詢到用戶信息 ==>> {}".format(id, res2))
if not res2: # 如果要刪除的用戶不存在於數據庫中,res2為空
return jsonify({"code": 3005, "msg": "刪除的用戶ID不存在,無法進行刪除,請檢查!!!"})
elif res2[0]["role"] == 0: # 如果要刪除的用戶是管理員用戶,則不允許刪除
return jsonify({"code": 3006, "msg": "用戶ID:【 {} 】,該用戶不允許刪除!!!".format(id)})
else:
sql3 = "DELETE FROM user WHERE id = {}".format(id)
db.execute_db(sql3)
print("刪除用戶信息SQL ==>> {}".format(sql3))
return jsonify({"code": 0, "msg": "恭喜,刪除用戶信息成功!"})
else:
return jsonify({"code": 3004, "msg": "當前用戶不是管理員用戶,無法進行操作,請檢查!!!"})
else:
return jsonify({"code": 3003, "msg": "token口令不正確,請檢查!!!"})
else:
return jsonify({"code": 3002, "msg": "當前用戶未登錄,請檢查!!!"})
else:
return jsonify({"code": 3001, "msg": "管理員用戶/token口令不能為空,請檢查!!!"})
相關的接口返回碼和請求場景如下:
接口返回碼 | 請求場景 |
---|---|
0 | 請求參數正確,刪除用戶成功! |
3001 | 請求參數中,管理員用戶/token口令,任一參數為空 |
3002 | 請求參數中,當前操作用戶沒有token,登錄驗證失敗 |
3003 | 請求參數中的token值,與redis中的token值不一致 |
3004 | 請求參數中,當前操作用戶不是管理員用戶,無權限進行操作 |
3005 | 請求參數中,要刪除的用戶ID不存在 |
3006 | 請求參數中,要刪除的用戶是管理員用戶,不允許刪除 |
可參考如下進行接口請求:
請求方式:POST
請求地址:http://127.0.0.1:5000/delete/user/5
請求頭:
Content-Type: application/json
Body:{"username": "wintest", "token": "wintest1587830406"}
OK,通過以上操作,我們已成功借助Redis來實現token登錄驗證,當然,我們redis的作用還有很多,在這里我們只是簡單的應用了一些,要學習的東西還是很多。相關代碼已上傳到GitHub,大家有興趣的可以基於此進行學習及開展接口測試。
GitHub源碼地址:https://github.com/wintests/flaskDemo