云计算百科
云计算领域专业知识百科平台

(一)HTTP 基本认证(Basic Authentication)原理过程,以及esp32 idf服务器和客户端例程

前言:通过同时讲诉服务器和客户端的处理情况,来理通思维,便于理解整个Basic Authentication认证过程.

一.先普及一下基础知识:

Base64 是一种将二进制数据转换为 ASCII 字符串的编码方式,使用 64 个可打印字符(A-Z, a-z, 0-9, +, /)表示二进制内容,并在末尾用 = 填充。其核心目的是解决二进制数据在纯文本协议(如 HTTP)中传输时的兼容性问题。

HTTP 基本认证(Basic Authentication) 作用:将用户名和密码拼接为 username:password 格式后 Base64 编码,置于 Authorization: Basic <凭证> 头中传输。例如:

GET /protected-resource HTTP/1.1

Authorization: Basic dXNlcjpwYXNzd2Q=

Base64优势
  • 文本协议兼容性 确保二进制数据(如加密哈希、文件内容)在 HTTP 头或 URL 中不因非 ASCII 字符被截断或转义。

  • 简易性与通用性

    • 所有编程语言均支持 Base64 编解码库(如 Python 的 base64、C 的 mbedtls_base64)。
    • 无需复杂配置,适合快速实现轻量级数据封装。
  • Base64局限性
  • 数据膨胀 Base64 编码后数据体积增加约 33%(每 3 字节二进制数据扩展为 4 字符),可能影响传输效率。

  • 无加密保护 Base64 仅编码而非加密,攻击者可轻易解码还原原始数据(建议配合 HTTPS 弥补安全性)。

  • 二.HTTP基本认证的原理

  • 认证流程
    • 步骤1:客户端首次请求 客户端发送不带认证信息的HTTP请求到服务器受保护资源:GET /protected-resource HTTP/1.1
    • 步骤2:服务器返回质询(Challenge) 服务器返回401 Unauthorized状态码,并在响应头中添加:WWW-Authenticate: Basic realm="SecureArea" 其中realm定义受保护资源的安全域,浏览器会弹出用户名/密码输入框。
    • 步骤3:客户端发送凭证 用户输入凭据后,客户端将username:password拼接后进行Base64编码,生成Authorization头:Authorization: Basic dXNlcjpwYXNzd29yZA==
    • 步骤4:服务器验证 服务器解码Base64字符串,分离用户名和密码进行校验,通过则返回资源,失败则重复步骤2。
  • 安全性问题
    • Base64仅为编码而非加密,凭证易被解码为明文,需配合HTTPS保证传输安全。
    • 无法防御重放攻击,攻击者可截获凭证后重复使用。
  • Base64编码依赖 使用mbedtls/base64.h库进行编码,需在menuconfig中启用MBEDTLS_BASE64_C组件。

  • esp32 idf实现细节:

  • 客户端发送请求访问受限区域,服务器返回401未授权,要求认证。客户端将用户名和密码拼接成“username:password”格式,进行Base64编码,然后放在Authorization头的Basic字段中发送。服务器解码验证,通过则返回资源。

    对于服务器部分,需要ESP-IDF的httpd组件,创建HTTP服务器。处理函数中,检查请求头中的Authorization字段,如果没有,发送401,并设置WWW-Authenticate头。如果有,解码Base64,验证用户名密码。

    对于客户端部分,使用esp_http_client组件。当发起请求时,如果服务器返回401,需要处理重试,或者直接预先添加Authorization头。自动处理401可能需要客户端自己处理,或者直接在第一次请求时添加Authorization头,避免401响应。

    详细步骤:

    服务器实现步骤:

    1. 配置并启动HTTP服务器。

    2. 注册处理程序,例如处理GET请求到某个URI。

    3. 在处理函数中,检查请求头的Authorization字段。

    4. 如果没有该字段,设置响应状态为401,添加WWW-Authenticate头,返回。

    5. 如果有,提取Basic后面的凭证,进行Base64解码。

    6. 拆分用户名和密码,与预设的进行比较。

    7. 如果匹配,返回请求的资源;否则返回403禁止访问。

    客户端实现步骤:

    1. 配置WiFi连接,连接到AP。

    2. 使用esp_http_client发起HTTP GET请求。

    3. 构造Authorization头,将用户名密码拼接后Base64编码。

    4. 将头添加到请求中,发送请求。

    5. 处理响应。

    现在,代码方面需要注意的点:

    服务器端:

    – 使用httpd_config_t配置服务器参数。

    – 创建URI处理函数,检查请求头。

    – 处理Base64解码,用户密码校验。

    – 需要包含必要的头文件,比如httpd.h,以及base64编解码函数。

    客户端端:

    – 使用esp_http_client_config_t配置客户端参数。

    – 生成Authorization头,其中Base64编码需要注意是否正确处理。

    – 可能需要手动添加头字段。

    Base64编码在ESP32中可以用mbedtls库中的函数,或者使用内置的base64编码函数。例如,esp-idf有提供base64_encode函数吗,或者需要自己实现,或者使用mbedtls的base64函数。

    例如,使用mbedtls_base64_encode函数来编码用户名和密码的字符串。

    注意,在拼接用户名密码时,中间用冒号连接,例如"admin:password",然后Base64编码。

    在服务器端,收到Authorization头后,取出Base64字符串,解码得到"username:password",然后分割比较。

    三.服务器代码

    ESP32 IDF 实现服务器端代码 实现一个带有 Basic 认证的 HTTP 服务器。

    #include <string.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "esp_system.h"
    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "esp_log.h"
    #include "nvs_flash.h"
    #include "esp_http_server.h"
    #include "mbedtls/base64.h"

    static const char *TAG = "http_server";
    #define WIFI_SSID      "YOUR_SSID"
    #define WIFI_PASS      "YOUR_PASSWORD"

    static httpd_handle_t server = NULL;

    // Basic 认证处理函数
    static esp_err_t basic_auth_handler(httpd_req_t *req) {
        const char *auth_header = httpd_req_get_hdr_value_str(req, "Authorization");
        if (!auth_header) {
            httpd_resp_set_status(req, "401 Unauthorized");
            httpd_resp_set_hdr(req, "WWW-Authenticate", "Basic realm=\\"Restricted Area\\"");
            httpd_resp_send(req, "Authentication Required", HTTPD_RESP_USE_STRLEN);
            return ESP_OK;
        }

        if (strncmp(auth_header, "Basic ", 6) != 0) {
            httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid auth header");
            return ESP_FAIL;
        }

        const char *encoded_creds = auth_header + 6;
        uint8_t decoded_creds[128];
        size_t out_len;
        int ret = mbedtls_base64_decode(decoded_creds, sizeof(decoded_creds), &out_len,
                                       (const unsigned char *)encoded_creds, strlen(encoded_creds));
        if (ret != 0) {
            httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Base64 decode failed");
            return ESP_FAIL;
        }
        decoded_creds[out_len] = '\\0';

        char *username = strtok((char *)decoded_creds, ":");
        char *password = strtok(NULL, ":");
        if (!username || !password) {
            httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid credentials");
            return ESP_FAIL;
        }

        // 验证用户名密码(示例为 admin/password123)
        if (strcmp(username, "admin") || strcmp(password, "password123")) {
            httpd_resp_send_err(req, HTTPD_403_FORBIDDEN, "Invalid credentials");
            return ESP_FAIL;
        }

        httpd_resp_send(req, "Authenticated Successfully!", HTTPD_RESP_USE_STRLEN);
        return ESP_OK;
    }

    // URI 路由配置
    static httpd_uri_t uri_handler = {
        .uri = "/",
        .method = HTTP_GET,
        .handler = basic_auth_handler,
        .user_ctx = NULL
    };

    // 启动 HTTP 服务器
    static void start_webserver() {
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        config.lru_purge_enable = true;

        if (httpd_start(&server, &config) == ESP_OK) {
            httpd_register_uri_handler(server, &uri_handler);
            ESP_LOGI(TAG, "HTTP server started");
        } else {
            ESP_LOGE(TAG, "Failed to start server");
        }
    }

    // WiFi 事件处理
    static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                                   int32_t event_id, void *event_data) {
        if (event_id == WIFI_EVENT_STA_START) {
            esp_wifi_connect();
        } else if (event_id == IP_EVENT_STA_GOT_IP) {
            start_webserver();
        }
    }

    // 初始化 WiFi
    static void wifi_init_sta() {
        esp_netif_init();
        esp_event_loop_create_default();
        esp_netif_create_default_wifi_sta();

        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        esp_wifi_init(&cfg);

        esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
        esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

        wifi_config_t wifi_config = {
            .sta = {
                .ssid = WIFI_SSID,
                .password = WIFI_PASS,
                .threshold.authmode = WIFI_AUTH_WPA2_PSK
            }
        };
        esp_wifi_set_mode(WIFI_MODE_STA);
        esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
        esp_wifi_start();
    }

    void app_main() {
        nvs_flash_init();
        wifi_init_sta();
    }

    四.客户端代码

    实现一个通过 Basic 认证访问服务器的客户端。

    #include <string.h>
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "esp_system.h"
    #include "esp_wifi.h"
    #include "esp_event.h"
    #include "esp_log.h"
    #include "nvs_flash.h"
    #include "esp_http_client.h"
    #include "mbedtls/base64.h"

    static const char *TAG = "http_client";
    #define WIFI_SSID      "YOUR_SSID"
    #define WIFI_PASS      "YOUR_PASSWORD"
    #define SERVER_URL     "http://SERVER_IP/"

    // HTTP 客户端任务
    static void http_client_task(void *pvParameters) {
        const char *username = "admin";
        const char *password = "password123";
        char credentials[64];
        snprintf(credentials, sizeof(credentials), "%s:%s", username, password);

        unsigned char encoded_creds[128];
        size_t encoded_len;
        int ret = mbedtls_base64_encode(encoded_creds, sizeof(encoded_creds), &encoded_len,
                                       (const unsigned char *)credentials, strlen(credentials));
        if (ret != 0) {
            ESP_LOGE(TAG, "Base64 encode failed: %d", ret);
            vTaskDelete(NULL);
            return;
        }
        encoded_creds[encoded_len] = '\\0';

        char auth_header[128];
        snprintf(auth_header, sizeof(auth_header), "Basic %s", encoded_creds);

        esp_http_client_config_t config = {
            .url = SERVER_URL,
            .method = HTTP_METHOD_GET,
        };
        esp_http_client_handle_t client = esp_http_client_init(&config);
        esp_http_client_set_header(client, "Authorization", auth_header);

        esp_err_t err = esp_http_client_perform(client);
        if (err == ESP_OK) {
            ESP_LOGI(TAG, "Status = %d, Content Length = %d",
                     esp_http_client_get_status_code(client),
                     esp_http_client_get_content_length(client));
        } else {
            ESP_LOGE(TAG, "Request failed: %s", esp_err_to_name(err));
        }
        esp_http_client_cleanup(client);
        vTaskDelete(NULL);
    }

    // WiFi 事件处理
    static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                                   int32_t event_id, void *event_data) {
        if (event_id == WIFI_EVENT_STA_START) {
            esp_wifi_connect();
        } else if (event_id == IP_EVENT_STA_GOT_IP) {
            xTaskCreate(http_client_task, "http_client_task", 4096, NULL, 5, NULL);
        }
    }

    // 初始化 WiFi
    static void wifi_init_sta() {
        esp_netif_init();
        esp_event_loop_create_default();
        esp_netif_create_default_wifi_sta();

        wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
        esp_wifi_init(&cfg);

        esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
        esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL);

        wifi_config_t wifi_config = {
            .sta = {
                .ssid = WIFI_SSID,
                .password = WIFI_PASS,
                .threshold.authmode = WIFI_AUTH_WPA2_PSK
            }
        };
        esp_wifi_set_mode(WIFI_MODE_STA);
        esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
        esp_wifi_start();
    }

    void app_main() {
        nvs_flash_init();
        wifi_init_sta();
    }

    说明

  • 配置 WiFi 信息 替换代码中的 YOUR_SSID、YOUR_PASSWORD 和 SERVER_IP。

  • 部署服务器和客户端

    • 服务器代码:运行在提供资源的 ESP32 上。

    • 客户端代码:运行在需要访问资源的 ESP32 上。

  • 验证流程

    • 客户端首次请求会收到 401,随后携带认证头重新请求。

    • 服务器验证通过后返回资源,否则返回 403。

  • 通过上述代码,ESP32 可实现完整的 HTTP 基本认证流程,适用于 IoT 设备的安全通信场景。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » (一)HTTP 基本认证(Basic Authentication)原理过程,以及esp32 idf服务器和客户端例程
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!