關於我
編程界的一名小小程序猿,目前在一個創業團隊任team lead,技術棧涉及Android、Python、Java和Go,這個也是咱們團隊的主要技術棧。
Github:github.com/hylinux1024
微信公衆號:angrycodemysql
本節開始項目的編碼實現。首先咱們來實現登陸註冊模塊的相關API
。本項目咱們是使用先後端分離的模式,在實現登陸註冊功能以前,假設咱們的接口是開放的,那麼須要肯定接口校驗方案。linux
咱們的目標是接口不能被抓包重複訪問,而且要對客戶端的可靠性進行驗證。git
timestamp
、nonce
、token
和 sign
appkey
和appsecret
參數一、timestamp
時間戳github
單位毫秒,也能夠是秒,與服務端保持一致;
時間戳是無關時區的,因此客戶端與服務端的時間戳是能夠用來比較的;
若是客戶端與服務端時間戳相差比較大,則能夠考慮使用服務端時間進行校準;
時間戳的做用是,保證這個請求在必定時間內(例如60秒內)是有效的。有效期內的校驗就須要nonce
參數redis
二、nonce
隨機數sql
由客戶端產生的隨機數,客戶端每次接口請求時須要保證它是不同的。
nonce
的做用是保證timestamp
有效期內的請求是不是合法的。
服務端接收到這個參數後,會將其保存在某個集合中。 服務端會檢測這個nonce
是否在該集合中出現過,若是出現過說明該請求是不合法的。
每一個nonce
的有效期設置跟timestamp
參數有關,例如能夠設置爲60秒。數據庫
三、token
登陸態編程
須要登陸的接口則須要一個token
參數。 服務端生成的token
在有效期內有效,若是token
過時則須要提示客戶端從新登陸。 token
的生成規則可以使用隨機數json
token = md5(1024位的隨機數)
複製代碼
四、sign
簽名或校驗參數flask
msg = 除了timestamp、nonce、token、sign參數以外的其它排序後的參數列表和值列表 = sort(參數1=值1&參數2=值2&參數3=值3...)
sign = md5(msg+token+timestamp+nonce+salt)
salt = 客戶端與服務端約定字符串
複製代碼
五、appkey
和 appsecret
服務端爲可信任的客戶端分配appkey
和appsecret
參數。可由隨機數或自定義的規則生成,要保證appkey
和appsecret
是對應的。
客戶端需保證appsecret
不被泄露。
客戶端接口請求時只需帶上appkey
參數。appsecret
則添加到sign
校驗參數的計算中
sign = md5(token+msg+timestamp+nonce+appsecret)
複製代碼
結合上面的參數,一個接口請求應該相似這樣
http://api.example.com/v1/login?phone=13499990000×tamp=1564486841415&nonce=34C2AF&sign=e10adc3949ba59abbe56e057f20f883e&appkey=A23CE80D
複製代碼
服務端程序接收到請求後驗證流程應該是這樣的
appkey
查詢到appsecret
,若是查不到則返回出錯信息,不然繼續;timestamp
檢查nonce
是否在有效時間內是的重複請求,若是是屢次重複請求,則返回出錯信息,不然繼續;msg
並計算sign
,將此參數與請求中獲取到的參數進行對比,驗證成功後纔開始咱們的業務邏輯。這樣咱們的一個簡單實用的接口驗證方案就出來了,固然可能還有其它一些好的想法,歡迎留言一塊兒探討學習。
如今開始實現登陸註冊功能,相信這個模塊走通了,以後其它模塊也是依樣畫葫蘆。
先看下模塊
├── api
│ ├── __init__.py
│ └── auth.py
├── app.py
├── config.ini
├── datingtoday.sql
├── models.py
├── requirements.txt
├── test
└── venv
複製代碼
增長了一個api
相關的文件包。 還有一個config.ini
,主要用於配置數據庫等信息,而models.py
文件是定義實體類的地方。
api/__init__.py
from flask import jsonify
def make_response_ok(data=None):
resp = {'code': 0, 'msg': 'success'}
if data:
resp['data'] = data
return jsonify(resp)
def make_response_error(code, msg):
resp = {'code': code, 'msg': msg}
return jsonify(resp)
def validsign(func):
""" 驗證簽名 :param func: :return: """
def decorator():
params = request.form
appkey = params.get('appkey')
sign = params.get('sign')
csign = signature(params)
if not appkey:
return make_response_error(300, 'appkey is none.')
if csign != sign:
return make_response_error(500, 'signature is error.')
return func()
return decorator
複製代碼
在__init__.py
中首先定義了兩個封裝統一的json
數據結構的的方法,主要是用到flask
中的jsonify
函數,它能夠把一個對象轉成json
。
在前面咱們講了接口的驗證邏輯,這一部分對參數的校驗功能實際上是能夠通用的,因此對這個邏輯也進行了封裝成validsign
方法。
不錯,這是一個裝飾器的定義。咱們但願在接口訪問的方法使用裝飾器,就能夠進行通用的接口校驗。
auth.py
這一節的重點是實現登陸註冊和發短信接口,所以建立一個 auth.py
的文件來寫跟受權登陸相關的接口,這樣有利於咱們組織代碼。
咱們知道要實現接口的訪問路徑的定義與方法直接的對應,是使用@route
這個裝飾器。這裏咱們在一個新文件中定義咱們的接口,就須要用到Blueprint
A blueprint is an object that allows defining application functions without requiring an application object ahead of time. It uses the same decorators as Flask, but defers the need for an application by recording them for later registration.
說白了,它的做用跟@route
差很少。
因爲咱們把登陸註冊看成一個接口來實現,即用戶經過短信進行登陸,後端會判斷該用戶是否爲新用戶,若是是新用戶則自動註冊。
首先定義接口的訪問路徑爲
{host:port}/api/auth/sendsms
請求方法:POST
參數:phone
請求成功
{
"code": 0,
"data": {
"code": "97532",
"phone": "18922986865"
},
"msg": "success"
}
複製代碼
根據接口定義咱們會在auth.py
中定義一個Blueprint
對象用來映射咱們的訪問路徑和方法。
bp = Blueprint("auth", __name__, url_prefix='/api/auth')
複製代碼
短信接口的實現這裏會使用到redis
,將請求到的短信驗證碼保存在redis
中,並設置過時時間。而後登陸時,再進行驗證。
@bp.route("/sendsms", methods=['POST'], endpoint="sendsms")
@validsign
def send_sms():
phone = request.form.get('phone')
m = re.match(pattern_phone, phone)
if not m:
return make_response_error(300, 'phone number format error.')
# 這裏須要修改成對接短信服務
code = '97532'
key = f'{phone}-{code}'
r.set(key, code, 60)
return make_response_ok({'phone': phone, 'code': code})
複製代碼
注意這裏的endpoint="sendsms"
是必需設置,由於@validsign
會修飾咱們的方法,每一個方法都是用一個通用的校驗,方法名稱會變成同樣的,因此若是不設置endpoint
會致使url
映射失敗。
首先定義接口的訪問路徑爲
{host:port}/api/auth/login
請求方法:POST
參數:phone
參數:code
請求成功
{
"code": 0,
"data": {
"expire_time": "2019-08-10 07:34:20",
"token": "5bea89727e7553284f162d35c9926414",
"user_id": 100784
},
"msg": "success"
}
複製代碼
執行登陸接口時,會先驗證redis
中的驗證碼,而後查一下受權表user_auth
看看是不是新用戶,最後返回用戶的登陸受權信息。
@bp.route("/login", methods=['POST'], endpoint='login')
@validsign
def login():
phone = request.form.get('phone')
code = request.form.get('code')
key = f'{phone}-{code}'
sms_code = r.get(key)
if sms_code:
sms_code = sms_code.decode()
if code != sms_code:
return make_response_error(503, 'sms code error')
auth_info = UserAuth.query.filter_by(open_id=phone).first()
if not auth_info:
auth_info = register_by_phone(phone)
else:
auth_info = login_by_phone(auth_info)
data = {'token': auth_info.token,
'expired_time': auth_info.expired_time.strftime("%Y-%m-%d %H:%M:%S"),
'user_id': auth_info.user_basic.id}
r.set(f'auth_info_{auth_info.user_id}', str(data))
return make_response_ok(data)
複製代碼
整體上邏輯仍是比較清晰的,最後咱們看一下app.py
from flask import Flask
from api import auth, config
from models import db
app = Flask(__name__)
# 將blueprint註冊到app中
app.register_blueprint(auth.bp)
# 配置app的config,將數據庫信息配置好
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config["SQLALCHEMY_DATABASE_URI"] = config['DATABASE']['uri']
# 最好生成一個secret_key
app.secret_key = '8c2c0b555e6e6cb01a5fd36dd981bcee'
db.init_app(app)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
複製代碼
配置文件config.ini
# 配置數據庫連接
[DATABASE]
uri = mysql+pymysql://user:password@127.0.0.1:3306/datingtoday
# 配置appkey和secret
[APP]
appkey = 432ABZ
appsecret = 1AE32B09224
複製代碼
因爲接口都須要動態計算校驗碼,因此單元測試是必需的。這裏我使用最簡單的方式,直接使用unittest
模塊。
例如測試發短信的業務接口,首先生成一個隨機數nonce
,而後計算校驗碼sign
參數,最後調用flask
中的post
方法模擬接口請求。
def test_sendsms(self):
import math
nonce = math.floor(random.uniform(100000, 1000000))
params = {'phone': '18922986865', 'appkey': '432ABZ', 'timestamp': datetime.now().timestamp(),
'nonce': nonce}
sign = signature(params)
params['sign'] = sign
respdata = self.app.post("/api/auth/sendsms", data=params)
resp = respdata.json
self.assertEqual(resp['code'], 0, respdata.data)
複製代碼
若是請求成功,就認爲經過測試。固然這裏的邏輯仍是比較簡單,但願小夥伴們留言討論。
源碼地址:
github.com/hylinux1024…
Flask官方地址:
palletsprojects.com/p/flask/
注意本文會使用到mysql
和redis
數據庫,須要自行安裝。