Разработка приложений REST API корпоративного уровня на основе Flask (2)

Python Flask RESTful

обо мне
Небольшой программист в мире программирования, в настоящее время работает руководителем команды в предпринимательской команде.Стек технологий включает Android, Python, Java и Go, который также является основным стеком технологий нашей команды.
Гитхаб:github.com/hylinux1024
Публичный аккаунт WeChat: гневный код

В этом разделе начинается кодирование реализации проекта. Сначала реализуемвойти Зарегистрироватьсясвязанный с модулемAPI. В этом проекте мы используемПереднее и заднее разделениережиме, прежде чем реализовать функцию входа и регистрации, предполагая, что наш интерфейс открыт, нам нужно определитьСхема проверки интерфейса.

0x00 Схема проверки интерфейса

Наша цель — взаимодействиеПовторный доступ к перехваченным пакетам невозможен., и кНадежность клиентааутентификация.

  • Атака против повтораДоступные параметры:timestamp,nonce,tokenиsign
  • Поддержка доверенных клиентовзапросы могут рассмотреть возможность добавленияappkeyиappsecretпараметр
общедоступный параметр

1,timestampотметка времени

Единица измерения — миллисекунды или секунды, в зависимости от сервера;
Временные метки не зависят от часового пояса, поэтому для сравнения можно использовать временные метки клиента и сервера;
Если отметка времени между клиентом и сервером сильно различается, вы можете рассмотреть возможность использования времени сервера для калибровки;
Роль метки времени заключается в том,Гарантировать, что запрос действителен в течение определенного периода времени (например, в течение 60 секунд). Требуется верификация в течение срока действияnonceпараметр

2,nonceслучайный номер

Случайное число, сгенерированное клиентом, клиент должен убедиться, что оно отличается каждый раз, когда запрашивает интерфейс.
nonceрольгарантироватьtimestampЯвляется ли запрос в течение срока действия законным.
После того, как сервер получит этот параметр, он сохранит его в коллекции. Сервер обнаружит этоnonceПоявился ли он в этом наборе, если есть, значит, запрос неправомерный.
каждыйnonceСрок годности устанавливается сtimestampСвязанный параметр, например, его можно установить на 60 секунд.

3.tokenсостояние входа

Интерфейс, требующий входа в систему, требуетtokenпараметр. генерируется серверомtokenДействует в течение срока действия, еслиtokenЕсли срок его действия истекает, клиенту необходимо снова войти в систему.tokenПравила генерации могут использовать случайные числа

token = md5(1024位的随机数)

4.signПараметры подписи или проверки

msg = 除了timestamp、nonce、token、sign参数之外的其它排序后的参数列表和值列表 = sort(参数1=值1&参数2=值2&参数3=值3...)

sign = md5(msg+token+timestamp+nonce+salt) 

salt = 客户端与服务端约定字符串

5.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&timestamp=1564486841415&nonce=34C2AF&sign=e10adc3949ba59abbe56e057f20f883e&appkey=A23CE80D

После того, как серверная программа получит запроспроцесс проверкидолжно быть так

  1. пройти черезappkeyЗапрошеноappsecret, если не найдено, вернуть сообщение об ошибке, иначе продолжить;
  2. пройти черезtimestampэкзаменnonceЯвляется ли это повторным запросом в течение допустимого времени, если он повторяется несколько раз, вернуть сообщение об ошибке, в противном случае продолжить;
  3. Создается по параметрам запросаmsgи рассчитатьsign, сравним этот параметр с параметрами, полученными в запросе, и после успешной проверки запустим нашу бизнес-логику.

Таким образом, у нас есть простая и практичная схема проверки интерфейса.Конечно, могут быть и другие хорошие идеи.Добро пожаловать, чтобы оставить сообщение для обсуждения и совместного изучения.

0x01 show me the code

Теперь начните реализовывать функцию входа и регистрации, я думаю, что этот модуль пройдет, и другие модули будут следовать той же схеме.

Первый взгляд на модуль

├── 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почти.

Так как мы реализуем логин и регистрацию как интерфейс, то есть пользователь авторизуется через смс, а бэкэнд будет определять, является ли пользователь новым пользователем, и если это новый пользователь, то он автоматически зарегистрируется.

0x02 СМС-интерфейс

Сначала определите путь доступа интерфейса как

{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')

Здесь будет использована реализация интерфейса SMSredis, сохраните запрошенный SMS-код подтверждения в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Ошибка сопоставления.

0x03 Интерфейс регистрации входа

Сначала определите путь доступа интерфейса как

{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

Модульный тест 0x04

Модульное тестирование необходимо, поскольку интерфейс должен динамически вычислять контрольную сумму. Здесь я использую самый простой способ, напрямую использую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)

Если запрос выполнен успешно, тест считается пройденным. Конечно, логика здесь относительно проста, и я надеюсь, что вы сможете оставить сообщение для обсуждения.

0x05 Адрес проекта

Адрес источника:
GitHub.com/ и Linux1024…

Официальный адрес фляги:
palletsprojects.com/p/flask/

Обратите внимание, что в этой статье будет использоватьсяmysqlиredisбазу данных, вам нужно установить ее самостоятельно.