1. 程式人生 > >Flask 實現 Rest API (03)

Flask 實現 Rest API (03)

Web 服務是無狀態的,那麼對於客戶端提交的請求,伺服器如何判斷請求的合法性呢?比如只能登陸成功的使用者,伺服器才按要求返回資料。本文提供一種利用 會話 (session) 機制實現儲存使用者登入資訊的方法。

會話是客戶端登入到伺服器並登出的時間間隔,對需要在此會話中儲存的資料存放在在伺服器上的臨時目錄中。伺服器端與每個客戶端的會話都有一個分配的會話ID (session ID)。

利用 session 記錄登入資訊

為了在伺服器端儲存使用者的登入資訊,需要在伺服器端儲存會話資料,瀏覽器每次請求的時候,都需要攜帶會話資料,伺服器端進行校驗。這種以使用者鑑權為目的的 session 也可也被稱為 token。大致的流程如下:

  1. 客戶端使用使用者名稱和密碼請求登入
  2. 服務端收到請求,驗證使用者名稱與密碼
  3. 驗證成功後,伺服器端會簽發一個 Token,再把這個 Token 傳送給客戶端
  4. 客戶端收到 Token 以後可以把它儲存起來,比如放在 Cookie 裡或者 Local Storage 裡
  5. 客戶端每次向伺服器端請求資源的時候需要帶著服務端簽發的 Token
  6. 伺服器端收到請求,去驗證客戶端請求裡面攜帶的 Token,如果驗證成功,就向客戶端返回請求的資料

Flask 實現 session

Flask 實現 session,對每個客戶端的會話分配一個會話 ID。 會話資料儲存在 Headers 的 cookie 中,伺服器以加密方式簽名。 對於這種加密,Flask application 需要定義一個 SECRET_KEY

,這是一個字串,可以理解為祕鑰,可以自由設定,儘量複雜為好。

app.config['SECRET_KEY'] = 'your_secret_key_here'

然後可以設定會話物件(字典型別的物件),比如在伺服器設定 username 的會話物件:

@main.route('/token', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    remember_user = request.json.
get('remember_user') if verify_user(username, password) == True: session['username'] = username if remember_user == True: session.permanent = True else: session.permanent = False return jsonify({'result': 1, 'description': 'Login successful.'}), 200 else: return jsonify({'result': 0, 'description': 'Login failed.'}), 401

設定 session 物件 的程式碼語句 session['username'] = username。 會話物件是一個字典物件。要刪除會話變數,使用 pop() 方法。

@main.route('/token', methods=['DELETE'])
def logout():
    if 'username' in session:
        session.pop('username')
        return jsonify({'result': 1,'description': 'Logout successful.'})
    else:
        return jsonify({'result': 0, 'description': 'No user was found.'})

設定 session 過期時間

登入之後,伺服器和你的瀏覽器之間建立了一個 session,它通常有一個過期時間,比如 30 分鐘,假設登入後 10 分鐘你進行了一次操作,發出 get 請求,那麼這個過期時間就要重新計算。從你操作的這一刻起,30 分鐘以後過期。這就意味著,如果你 30 分鐘內沒有任何操作,session 就會過期,必須重新登入。

Flask 設定 session 的過期時間需要兩條語句:

app.permanent_session_lifetime = datetime.timedelta(seconds=30*60)
session.permanent = True

這樣就設定了 session 的過期時間為 30 分鐘。

完整程式碼

# token.py

# encoding: utf-8

from .. import db

def get_users():
    conn = db.connect()
    curr = conn.cursor()

    curr.execute('select username, pwd as password from users')

    users_dict = [dict((curr.description[i][0], value)
        for i, value in enumerate(rows))
        for rows in curr.fetchall()]

    curr.close()
    conn.close()

    return users_dict


def get_password(user_name):
    result = ''
    users = get_users()
    for user in users:
        if user['username'].upper() == user_name.upper():
            result = user.get('password')

    return result


def verify_user(user_name, password):
    if password == get_password(user_name):
        return True
    else:
        return False
# token_routes.py

# encoding: utf-8

from . token import *
from . import main
from flask import request, session, jsonify

@main.route('/token', methods=['POST'])
def login():
    username = request.json.get('username')
    password = request.json.get('password')
    remember_user = request.json.get('remember_user')

    if verify_user(username, password) == True:
        session['username'] = username
        if remember_user == True:
            session.permanent = True
        else:
            session.permanent = False

        return jsonify({'result': 1, 'description': 'Login successful.'}), 200
    else:
        return jsonify({'result': 0, 'description': 'Login failed.'}), 401


@main.route('/token', methods=['DELETE'])
def logout():
    if 'username' in session:
        session.pop('username')
        return jsonify({'result': 1,'description': 'Logout successful.'})
    else:
        return jsonify({'result': 0, 'description': 'No user was found.'})

路由中新增 session 校驗

在伺服器端,對相關的檢視函式 (view function)進行校驗:

@main.route('/employees')
def listEmployees():
    if 'username' in session:
        emp = Employee();
        employees = emp.listAll()
        return jsonify({'rows': employees}), 200;
    else:
        return jsonify({'error': 'No authorization.'}), 401

客戶端測試

使用自己喜歡的 Rest 測試工具,比如 Postman。本文提供利用 requests 庫進行請求的示例:

import requests
import json


def get_session_cookie():
    result = ''

    payload = {
        "username": "admin",
        "password": "pwd"
    }

    resp = requests.post('http://localhost:5000/token', json=payload)

    for k, v in resp.cookies.items():
        if k == 'session':
            result = v

    return result

cookie = get_session_cookie()

def get_employees():
    session_cookie = {
        'session': cookie
    }
    resp = requests.get('http://localhost:5000/employees', cookies=session_cookie)

    return resp


def create_employee():
    session_cookie = {
        'session': cookie
    }

    payload = {
        "AGE": 26,
        "EDUCATION": "Lower secondary",
        "EMAIL": "[email protected]",
        "EMP_ID": 9001,
        "GENDER": "Female",
        "MARITAL_STAT": "Married",
        "NR_OF_CHILDREN": 4,
        "PHONE_NR": "980-0639-38"
    }

    resp = requests.post(
        'http://localhost:5000/employees/create',
        json=payload,
        cookies=session_cookie
    )

    return resp


def modify_employee():
    session_cookie = {
        'session': cookie
    }

    payload = {
        "AGE": 26,
        "EDUCATION": "Lower secondary",
        "EMAIL": "[email protected]",
        "EMP_ID": 9001,
        "GENDER": "女",
        "MARITAL_STAT": "Married",
        "NR_OF_CHILDREN": 4,
        "PHONE_NR": "980-0639-38"
    }

    resp = requests.put(
        'http://localhost:5000/employees/9001',
        json=payload,
        cookies=session_cookie)

    return resp


def delete_employee():
    session_cookie = {
        'session': cookie
    }

    resp = requests.delete(
        'http://localhost:5000/employees/9001',
        cookies=session_cookie)

    return resp

參考