Гид по технологиям

Как сделать проверку OTP на Python с отправкой SMS через Twilio

6 min read Разработка Python Обновлено 19 Dec 2025
Проверка OTP на Python через Twilio
Проверка OTP на Python через Twilio

Человек пишет код на ноутбуке

Введение

Одноразовые коды (OTP) — удобный и часто используемый второй фактор аутентификации. Даже при компрометации пароля пользовательская сессия остаётся защищённой, если правильный OTP известен только владельцу телефона. В этой инструкции мы шаг за шагом реализуем настольное приложение на Python, которое:

  • генерирует 4-значный OTP;
  • отправляет его по SMS через Twilio;
  • делает код действительным 2 минуты;
  • блокирует учётную запись после 3 неверных вводов на 10 минут;
  • предоставляет кнопки «Отправить», «Отправить повторно» и «Проверить».

Определения в одну строку:

  • OTP — одноразовый пароль (One-Time Password).
  • Twilio — облачный сервис для отправки SMS и звонков через API.
  • Tkinter — стандартная библиотека Python для графического интерфейса.

Что потребуется

  • Установленный Python 3.7+.
  • Учётная запись Twilio и телефонный номер в консоли Twilio.
  • Пакеты: twilio и tk (Tkinter обычно встроен в стандартную поставку Python на Windows и macOS).

Команда установки (выполните в терминале):

pip install twilio tk

Важно: расходы на SMS зависят от тарифа Twilio в вашей стране. Следите за счётом в консоли Twilio и используйте тестовые номера при разработке.

Получение учётных данных Twilio и номера телефона

  1. Зарегистрируйтесь на сайте Twilio и войдите в консоль.
  2. В разделе консоли найдите кнопку “Get phone number” и получите номер.

Получение номера телефона в консоли Twilio

  1. В разделе “Account Info” скопируйте Account SID и Auth Token — они понадобятся для клиента API.

Копирование учётных данных Twilio из консоли

Сохраняйте эти данные безопасно и не публикуйте в открытых репозиториях.

Структура приложения и полный пример кода

Ниже — полный пример приложения. В нём локализованы UI-строки (на русском), но логика полностью соответствует требованиям. Замените значения YOUR_ACCOUNT_SID, YOUR_AUTH_TOKEN и TWILIO_MOBILE_NUMBER на ваши реальные значения.

import tkinter as tk
from tkinter import messagebox
from twilio.rest import Client
import random
import threading
import time

# Замените на свои данные из консоли Twilio
account_sid = "YOUR_ACCOUNT_SID"
auth_token = "YOUR_AUTH_TOKEN"
client = Client(account_sid, auth_token)

# Время жизни OTP в секундах (2 минуты)
expiration_time = 120

class OTPVerification:
    def __init__(self, master):
        self.master = master
        self.master.title('Проверка OTP')
        self.master.geometry("600x275")

        self.otp = None
        self.timer_thread = None
        self.resend_timer = None
        self.wrong_attempts = 0
        self.locked = False
        self.stop_timer = False

        # Метка и поле ввода номера телефона
        self.label1 = tk.Label(self.master, text='Введите номер мобильного телефона:', font=('Arial', 14))
        self.label1.pack()

        self.mobile_number_entry = tk.Entry(self.master, width=20, font=('Arial', 14))
        self.mobile_number_entry.pack()

        # Кнопки: отправить OTP и повторно отправить
        self.send_otp_button = tk.Button(self.master, text='Отправить OTP', command=self.send_otp, font=('Arial', 14))
        self.send_otp_button.pack()

        self.timer_label = tk.Label(self.master, text='', font=('Arial', 12, 'bold'))
        self.timer_label.pack()

        self.resend_otp_button = tk.Button(self.master, text='Отправить снова', state=tk.DISABLED, command=self.resend_otp, font=('Arial', 14))
        self.resend_otp_button.pack()

        # Поле ввода OTP и кнопка проверки
        self.label2 = tk.Label(self.master, text='Введите OTP, отправленный на ваш телефон:', font=('Arial', 14))
        self.label2.pack()

        self.otp_entry = tk.Entry(self.master, width=20, font=('Arial', 14))
        self.otp_entry.pack()

        self.verify_otp_button = tk.Button(self.master, text='Проверить OTP', command=self.verify_otp, font=('Arial', 14))
        self.verify_otp_button.pack()

    def start_timer(self):
        self.timer_thread = threading.Thread(target=self.timer_countdown)
        self.timer_thread.start()

    def timer_countdown(self):
        start_time = time.time()
        while True:
            current_time = time.time()
            elapsed_time = current_time - start_time
            remaining_time = expiration_time - elapsed_time
            if self.stop_timer:
                break
            if remaining_time <= 0:
                messagebox.showerror('Ошибка', 'OTP истёк.')
                self.resend_otp_button.config(state=tk.NORMAL)
                self.otp = None
                break
            minutes = int(remaining_time // 60)
            seconds = int(remaining_time % 60)
            timer_label = f'Осталось времени: {minutes:02d}:{seconds:02d}'
            self.timer_label.config(text=timer_label)
            time.sleep(1)

    def send_otp(self):
        if self.locked:
            messagebox.showinfo('Учётная запись заблокирована', 'Ваша учётная запись заблокирована. Попробуйте позже.')
            return
        mobile_number = self.mobile_number_entry.get()
        if not mobile_number:
            messagebox.showerror('Ошибка', 'Пожалуйста, введите номер мобильного телефона.')
            return

        self.otp = random.randint(1000, 9999)
        message = client.messages.create(
            body=f'Ваш OTP: {self.otp}.',
            from_='TWILIO_MOBILE_NUMBER',
            to=mobile_number
        )
        messagebox.showinfo('OTP отправлен', f'OTP был отправлен на {mobile_number}.')
        self.start_timer()
        self.send_otp_button.config(state=tk.DISABLED)
        self.resend_otp_button.config(state=tk.DISABLED)
        self.otp_entry.delete(0, tk.END)

    def resend_otp(self):
        if self.locked:
            messagebox.showinfo('Учётная запись заблокирована', 'Ваша учётная запись заблокирована. Попробуйте позже.')
            return
        mobile_number = self.mobile_number_entry.get()
        if not mobile_number:
            messagebox.showerror('Ошибка', 'Пожалуйста, введите номер мобильного телефона.')
            return

        self.otp = random.randint(1000, 9999)
        message = client.messages.create(
            body=f'Ваш новый OTP: {self.otp}.',
            from_='TWILIO_MOBILE_NUMBER',
            to=mobile_number
        )
        messagebox.showinfo('OTP отправлен', f'Новый OTP был отправлен на {mobile_number}.')
        self.start_timer()
        self.resend_otp_button.config(state=tk.DISABLED)

    def verify_otp(self):
        user_otp = self.otp_entry.get()
        if not user_otp:
            messagebox.showerror('Ошибка', 'Пожалуйста, введите OTP.')
            return
        if self.otp is None:
            messagebox.showerror('Ошибка', 'Сначала сгенерируйте OTP.')
            return
        try:
            if int(user_otp) == self.otp:
                messagebox.showinfo('Успех', 'OTP успешно подтверждён.')
                self.stop_timer = True
                exit()
            else:
                self.wrong_attempts += 1
                if self.wrong_attempts >= 3:
                    self.lock_account()
                else:
                    messagebox.showerror('Ошибка', 'OTP не совпадает.')
        except ValueError:
            messagebox.showerror('Ошибка', 'OTP должен быть числом.')

    def lock_account(self):
        self.locked = True
        self.label1.config(text='Учётная запись заблокирована')
        self.mobile_number_entry.config(state=tk.DISABLED)
        self.send_otp_button.config(state=tk.DISABLED)
        self.timer_label.config(text='')
        self.resend_otp_button.config(state=tk.DISABLED)
        self.label2.config(text='')
        self.otp_entry.config(state=tk.DISABLED)
        self.verify_otp_button.config(state=tk.DISABLED)
        self.stop_timer = True
        countdown_time = 10 * 60
        self.start_countdown(countdown_time)

    def start_countdown(self, remaining_time):
        if remaining_time <= 0:
            self.reset_account()
            return
        minutes = int(remaining_time // 60)
        seconds = int(remaining_time % 60)
        timer_label = f'Учётная запись заблокирована. Попробуйте через: {minutes:02d}:{seconds:02d}'
        self.timer_label.config(text=timer_label)
        self.master.after(1000, self.start_countdown, remaining_time - 1)

    def reset_account(self):
        self.locked = False
        self.wrong_attempts = 0
        self.label1.config(text='Введите номер мобильного телефона:')
        self.mobile_number_entry.config(state=tk.NORMAL)
        self.send_otp_button.config(state=tk.NORMAL)
        self.timer_label.config(text='')
        self.resend_otp_button.config(state=tk.DISABLED)
        self.label2.config(text='Введите OTP, отправленный на ваш телефон:')
        self.otp_entry.config(state=tk.NORMAL)
        self.verify_otp_button.config(state=tk.NORMAL)
        self.stop_timer = False

if __name__ == '__main__':
    root = tk.Tk()
    otp_verification = OTPVerification(root)
    root.mainloop()

Важно: в рабочей системе не стоит хранить SID и токен в коде. Используйте переменные окружения или секретный менеджер.

Разбор ключевых частей кода

  • Генерация OTP: random.randint(1000, 9999) — удобный простой вариант для 4-значного кода. Для большей безопасности используйте криптографически стойкие генераторы (secrets.choice).
  • Отправка SMS: client.messages.create(…) — создаёт и отправляет сообщение через Twilio.
  • Таймер: таймер запускается в отдельном потоке, чтобы не блокировать GUI.
  • Блокировка: после трёх неверных попыток приложение отключает поля и запускает обратный отсчёт 10 минут.

UX и валидация номера

Рекомендации по валидации номера:

  • Требуйте международный формат: +7XXXXXXXXXX или +1XXXXXXXXXX.
  • Проверяйте длину и символы (только цифры и ведущий ‘+’).
  • На этапе разработки можно использовать тестовые номера Twilio.

Когда такая схема НЕ подойдёт (контрпримеры)

  • Многочисленные автоматизированные запросы: если ваш сервис подвергается массированной атаке по SMS, счёт Twilio быстро увеличится в расходах.
  • Работа в условиях отсутствия мобильной связи: OTP по SMS не доставляется в офлайн-режиме.
  • Высокие требования к безопасности: для критичных систем используйте аппаратные токены или приложения-генераторы (TOTP) вместо SMS.

Альтернативные подходы

  • TOTP (Time-based One-Time Password) по RFC 6238: коды генерируются автономно в приложении (Google Authenticator). Подходит при необходимости отказа от SMS.
  • Push-уведомления: приложение на мобильном устройстве получает запрос о подтверждении входа (более удобный UX и снижает риск перехвата SMS).
  • Голосовые OTP: Twilio может позвонить и зачитать код, полезно для устройств без SMS.

Ментальные модели и эвристики при проектировании

  • Слойности: аутентификация = пароль + второй фактор. Каждый фактор должен быть независимым.
  • Минимизация времени жизни секретов: короткие TTL уменьшает окно атаки.
  • Оборотность: возможность безопасной повторной отправки, но с ограничением по частоте.

Безопасность и жёсткие рекомендации

  • Не храните SID/AuthToken в репозитории. Используйте переменные окружения (os.environ) или секреты CI/CD.
  • Логи: не логируйте сами OTP и не отправляйте их в централизованные логи.
  • Rate limiting: добавьте защиту от частых запросов (IP и учётная запись).
  • Используйте TLS при взаимодействии с API (Twilio SDK использует HTTPS по умолчанию).
  • Для генерации кодов используйте модуль secrets для криптографической стойкости:
import secrets
otp = secrets.randbelow(9000) + 1000  # 1000–9999

Конфиденциальность и соответствие (GDPR)

  • Обрабатывая номера телефонов, учитывайте обязанности по защите персональных данных.
  • Храните минимально возможный объём данных и удаляйте временные значения (OTP) сразу после использования или истечения.
  • При необходимости уведомляйте пользователей об обработке номеров и получении согласия на отправку SMS.

Тест-кейсы и критерии приёмки

Критерии приёмки (минимум):

  • При вводе валидного номера и нажатии «Отправить OTP» приходит SMS с 4-значным кодом.
  • Код действует ровно 120 секунд; после истечения он недействителен, и кнопка “Отправить снова” становится активной.
  • После трёх неверных вводов поля становятся недоступны и отображается таймер 10 минут.
  • После 10 минут состояние аккаунта сбрасывается и можно повторить попытку.

Тест-кейсы:

  1. Happy path: корректный номер → получение OTP → ввод правильного кода → успешная верификация.
  2. Время истекло: получить код → ждать 121 секунду → попытаться ввести код → получить сообщение об истечении.
  3. Неверный код три раза → учётная запись блокируется → после 10 минут доступ восстанавливается.
  4. Пустой ввод номера/OTP → соответствующие сообщения об ошибке.
  5. Попытка отправлять OTP многократно → проверка ограничения частоты.

Чеклист ролей (разработчик / администратор / тестировщик)

Разработчик:

  • Использовать secrets для генерации при нужде.
  • Вынести SID и токен в переменные окружения.
  • Добавить логирование ошибок, но не OTP.

Администратор:

  • Настроить квоты в Twilio и оповещения по расходам.
  • Подключить мониторинг и оповещения по ошибкам доставки SMS.

Тестировщик:

  • Подготовить тестовые номера Twilio.
  • Проверить все тест-кейсы и сценарии отказа.

Простая методология внедрения (mini-SOP)

  1. Настройте тестовый аккаунт Twilio и неблокирующий номер.
  2. Разработайте MVP с отключённым реальным отправлением (мокируйте API).
  3. Тестируйте локально и в staging с реальными SMS на тестовые номера.
  4. Перейдите в production после верификации нагрузки и контроля расходов.

Решение для сложных сценариев — схема принятия решений

flowchart TD
  A[Пользователь просит OTP] --> B{Номер валиден?}
  B -- Да --> C[Сгенерировать OTP и отправить SMS]
  B -- Нет --> D[Попросить корректный номер]
  C --> E[Запустить таймер 120s]
  E --> F{Пользователь ввёл код?}
  F -- Да --> G{Код верный?}
  G -- Да --> H[Успех]
  G -- Нет --> I[+1 к неверным]
  I --> J{Неверных >=3?}
  J -- Да --> K[Заблокировать на 10 минут]
  J -- Нет --> L[Показать ошибку, позволить повторы]
  F -- Нет --> M[Ожидание или истечение времени]
  M --> N[Если истёк → позволить повторную отправку]

(Этот диаграммный поток можно использовать как опорную схему для архитектуры.)

Советы по эксплуатации и масштабированию

  • Если пользователей много, добавьте очередь отправки SMS и отдельный сервис, отвечающий за коммуникации.
  • Учитывайте международные тарифы и использование локальных провайдеров через интеграции Twilio для снижения стоимости.
  • Для входа на мобильных устройствах рассмотрите push-уведомления вместо SMS.

Примеры вывода и визуализация

При запуске программы вы увидите окно ввода номера и кнопки. Интерфейс подсказывает, когда кнопки недоступны (через деактивацию) и отображает оставшееся время.

Стартовый экран программы проверки OTP

При вводе верного кода вы увидите подтверждение и выход приложения.

Правильный OTP введён в программе

При ошибках вы получите соответствующие сообщения.

Неправильный OTP введён в программе

Если три раза подряд ввести неверный код, интерфейс заблокируется на 10 минут.

Экран заблокированной учётной записи в программе

Глоссарий (одна строка)

  • SID: идентификатор аккаунта Twilio;
  • Auth Token: секретный токен для API Twilio;
  • TTL: время жизни одноразового кода;
  • Rate limiting: ограничение частоты запросов.

Сравнение: SMS OTP vs TOTP (коротко)

  • SMS OTP: простая интеграция, зависит от оператора и уязвима к SIM-свопу.
  • TOTP: не зависит от сети, безопаснее, требует установки приложения у пользователя.

Резюме

  • SMS-OTP легко реализуется через Twilio и Python/Tkinter.
  • Главные риски — перехват SMS, SIM-своп и расходы на сообщения.
  • Для продакшена добавьте rate-limiting, хранение секретов в безопасном хранилище и мониторинг по затратам.

Важно: начните с тестовой среды Twilio и ограничьте отправку SMS на этапе разработки.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Полная настройка пульта Logitech Harmony
Гайды

Полная настройка пульта Logitech Harmony

Ссылки в macOS: алиасы, символьные и жесткие
macOS

Ссылки в macOS: алиасы, символьные и жесткие

Установить Windows 8 в VHD без переразметки
Windows

Установить Windows 8 в VHD без переразметки

Dream Address в Animal Crossing: как пользоваться
Игры

Dream Address в Animal Crossing: как пользоваться

Как продавать электронные книги — пошагово
Издательство

Как продавать электронные книги — пошагово

Восстановить несохранённый документ Word
Microsoft Word

Восстановить несохранённый документ Word