Micropyserver. Реализуем Basic Auth для IoT устройств +4


AliExpress RU&CIS

Что такое micropyserver?

Micropyserver - это простая библиотека для реализации HTTP сервера на базе устройств типа ESP8266 или ESP32 написанная на Micropython. Скачать ее можно тут.

Что пытаемся сделать?

Часто нужно ограничить доступ к веб-интерфейсу своего IoT устройства из вне, так как в интерфейсе могут располагаться элементы управления или данные которые мы не хотим сделать доступными внешнему миру. Стандартном средством ограничения доступа к веб-интерфейсу служит метод аутенфикации, т.е. для доступа к нашему устройству мы будем спрашивать логин и пароль пользователя. Для решения задачи аутенфикации мы воспользуемся таким механизмом как Basic Auth, этот метод самый простой в реализации так как не требует ни использования cookies, ни наличия механизма сессий, ни специальной HTML страницы и формы для аутенфикации.

Что такое Basic Auth?

Механизм Basic Auth поддерживается всеми современными браузерами. Он не обеспечивает защиту конфиденциальности передаваемых учетных данных. Логин и пароль пользователя просто закодированы с помощью Base64 в HTTP заголовке с названием Authorization, они не зашифрованы и не хэшируются каким - либо образом. Поэтому для повышения безопасности этот механизм лучше использовать в сочетании с безопасным протоколом HTTPS. Поскольку данные о пароле и логине должны быть отправлены в заголовке каждого HTTP-запроса, веб-браузеру необходимо кэшировать введенные учетные данные в течение разумного периода времени, чтобы избежать постоянного запроса пользователя на ввод имени пользователя и пароля. Политика кеширования различается в зависимости от браузера. В современных браузерах кэшированные учетные данные для базовой проверки подлинности обычно очищаются при очистке истории просмотров. Большинство браузеров позволяют пользователям специально очищать только учетные данные, хотя этот параметр может быть трудно найти, и обычно очищает учетные данные для всех посещаемых сайтов.

На стороне браузера механизм авторизации построен следующим образом:

  • Логин и пароль объединяются одним двоеточием (:). Это означает, что само имя пользователя не может содержать двоеточие;

  • Результирующая строка кодируется в последовательность октетов. Набор символов, используемый для этой кодировки, по умолчанию не указан, если он совместим с US-ASCII, но сервер может предложить использовать UTF-8, отправив параметр charset;

  • Результирующая строка кодируется с использованием Base64;

  • Затем к закодированной строке добавляется метод авторизации и пробел (например, «Basic»);

Например, если браузер использует Aladdin в качестве логина и open sesame в качестве пароля, то значение поля - это кодировка Base64 для Aladdin:open sesame или QWxhZGRpbjpvcGVuIHNlc2FtZQ== . Тогда заголовок авторизации будет выглядеть так: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Со стороны сервера обработка запроса авторизации происходит так:

  • когда сервер хочет, чтобы браузер аутентифицировал себя по отношению к серверу, сервер должен соответствующим образом отвечать на не аутентифицированные запросы;

  • для запросов, не прошедших аутентификацию, сервер должен возвращать ответ, заголовок которого содержит статус HTTP 401 Unauthorized и заголовок WWW-Authenticate;

Реализуем Basic Auth

Теперь, когда стал понятен механизм авторизации, давайте напишем реализацию авторизации с использованием библиотеки HTTP сервера micropyserver.

Для хранения связи пароль/логина мы будем использовать json формат данных и хранить их в отдельном от кода файле с названием credentials.json:

{
	"user": "admin",
	"password": "admin"
}

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

Сам код будет располагаться в файле main.py:

from micropyserver import MicroPyServer
import re
import base64
import json
import network
import esp

wlan_id = "your wi-fi"
wlan_pass = "your password"


wlan = network.WLAN(network.STA_IF)
wlan.active(True)


while not wlan.isconnected():
    wlan.connect(wlan_id, wlan_pass)
print("Connected... IP: " + wlan.ifconfig()[0]);


server = MicroPyServer()
''' secret page '''
def my_secret_page(request):
	server.send("MY SECRET PAGE")
''' add route '''
server.add_route("/", my_secret_page)


def on_request(request, address):
    ''' authorization '''
    headers = request.split("\r\n")
    need_auth = True
    for header in headers:
        authorization_header = re.search("^Authorization:\\s+Basic\\s+(.+)", header)
        if authorization_header is None:
            continue
        credentials_base64_str = authorization_header.group(1)

        if credentials_base64_str:
            credentials_pair = base64.b64decode(credentials_base64_str.encode("utf8")).decode("utf8")
            user_credentials = credentials_pair.split(":")
            with open("credentials.json", "r") as credentials_file:
                credentials = json.load(credentials_file)
                need_auth = credentials["user"] != user_credentials[0] or credentials["password"] != user_credentials[1]
    if need_auth:
        server.send("HTTP/1.0 401\r\n")
        server.send("WWW-Authenticate: Basic realm='User Visible Realm'\r\n\r\n")
        return False
    else:
        return True


''' add on request handler '''
server.on_request(on_request)
''' start server '''
server.start()
 

Что мы сделали в этом примере? Мы использовали обработчик запросов on_request, который вызывается при каждом запросе пользователя. В нем проверяем мы правильность логина и пароля и если они не правильные, то отсылаем заголовки требующие авторизацию: HTTP 401 Unauthorized и WWW-Authenticate и возвращаем False, что бы сказать серверу, что бы он дальше не обрабатывал запрос. Если логин и пароль правильные, то не отсылаем никакие заголовки и просто возвращаем True в обработчике.

Пробуем код

Если ввести в браузере http://IP-УСТРОЙСТВА, то мы получим предложение ввести логин и пароль, что бы получить доступ к устройству вам надо ввести логин admin и пароль admin (см. файл сredentials.json) и только после этого вы попадёте на секретную страницу на которой будет написано "MY SECRET PAGE".

Что можно улучшить?

У нас есть небольшая проблема, мы храним связку логин пароль в открытом виде, но по-хорошему нам надо хранить пароль в зашифрованном виде, допустим в виде значения хеша md5 и при проверке авторизации сравнивать именно хеши полученного и сохраненного пароля. Зачем? Для того что бы при краже или взломе устройства наш пароль не стал известен злоумышленнику. Но это уже для тех, кто хочет еще более сделать безопасным свое устройство, для большинства пользователей думаю будет это излишне.

Разлогин

HTTP протокол не предоставляет веб-серверу метод, позволяющий клиенту «выйти из системы». Однако существует ряд методов для очистки кешированных учетных данных в определенных веб-браузерах. Один из них - перенаправление пользователя на URL-адрес в том же домене с использованием намеренно неверных учетных данных. Однако такое поведение несовместимо между различными браузерами и версиями браузеров.




Комментарии (3):

  1. propell-ant
    /#22759766

    Поэтому для повышения безопасности этот механизм лучше использовать в сочетании с безопасным протоколом HTTPS
    Скажем прямо, этот механизм без HTTPS не дает ничего, кроме иллюзии безопасности.
    Micropyserver — это простая библиотека для реализации HTTP сервера
    Говоря по русски — micropyserver не умеет HTTPS.

    Теперь складываем 2 и 2:
    Статья о том как сделать аутентификацию. Но без HTTPS она ни от чего не защищает, а HTTPS не предусмотрен используемой либой.
    Зачем тогда аутентификация?

    • bios737
      /#22759854

      Все верно. Проголосовать плюсиком не могу. Поэтому соглашусь в ответе. Для таких нужд у себя поднял VPN на mikrotik и спокойно управляю устройствами. Авторизация приведенная выше, имеет право на жизнь в локальной защищенной сети, например дома, чтоб дети не игрались с настройками, случайно попав на страничку.

  2. sav13
    /#22762568

    Немного не по теме статьи. А есть микропитон не в виде монолитной прошивки ESP, а в виде библиотеки? Чтобы на ESP32 запустить его в одной из задач FreeRTOS?