这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 【AtomS3R-M12】DIY:EEPWHTTPBasic认证项目

共1条 1/1 1 跳转至页

【AtomS3R-M12】DIY:EEPWHTTPBasic认证项目

高工
2026-11-12 20:15:14     打赏

设备上电后以 STA 模式接入无线网络“航天科宇”,使用固定地址 192.168.10.230 提供服务,在 80 端口启动 HTTP 服务。访问根路径时必须通过 HTTP Basic 认证:认证成功返回欢迎页面,认证失败则在登录弹窗中提示“密码错误,请重新登录”。设备同时开启 Arduino OTA,支持不插 USB 的无线固件升级。

成功页面共三行内容,依次为“登录成功”“欢迎进入EEPW世界”和“作者:keyboard007”,整体在浏览器窗口中居中显示。

1.3 运行环境

硬件环境为 M5Stack AtomS3R 开发板,板载 ESP32-S3,支持 2.4GHz 频段无线网络。

软件环境为 Arduino IDE 或 arduino-cli,配套 esp32 core 3.3.9。

编译参数:开发板选择 M5AtomS3,对应的 FQBN 为 esp32:esp32:m5stack_atoms3;上传端口为 COM15;串口监视器波特率为 115200。

访问方式:在同一网段的电脑浏览器中打开 http://192.168.10.230/。

1.4 账号说明

凭据表内置五组账号,分别为 admin/esp32、fred/41234123、charlie/sdfsd、alice/vambdnkuhj、bob/svcdbjhws12。账号以结构体数组的形式保存在固件中,新增或删除账号只需修改数组内容,条目数量由 sizeof 自动计算,不必改动其他逻辑。

第二章 设计思路

2.1 总体架构

程序自上而下分为四层。网络连接层负责 WiFi 接入与静态地址配置;服务层由 WebServer 监听 80 端口并分发请求;认证层通过回调函数完成用户名到密码的映射;渲染层统一生成带样式的 HTML 页面。四层之间通过函数调用串联,结构简单,出现问题时可以逐层定位。

2.2 认证方案选择

WebServer 库提供两类认证接口:一类是固定用户名密码的 authenticate(username, password),只能校验单一账号;另一类是带回调的 authenticate(函数指针),适合多用户场景。回调签名为 String *(HTTPAuthMethod mode, String username, String params[]),返回该用户名对应的密码指针,返回 NULL 表示用户不存在。

真正的 base64 解码、密码比对、恒定时间比较以及返回值的内存释放都由库内部完成,业务代码只承担“查表”这一件事,职责清晰,也不容易写出内存泄漏或二次释放。返回类型之所以用指针而非字符串,是为了多表达一种“查无此人”的状态,这是回调式认证与固定账号认证在设计上的关键差别。

2.3 凭据表设计

用 typedef struct 定义 credentials_t,包含 username 与 password 两个 String 成员;再用数组集中保存全部账号;最后用 sizeof(passwdfile) / sizeof(credentials_t) 计算条目数。这样增删账号不会遗漏循环边界,也避免了手写数量与内容不一致的常见错误。

2.4 错误提示设计

HTTP Basic 认证由浏览器负责弹出登录框,在弹窗显示期间页面正文不会渲染,因此“密码错误”的提示只能借助响应头 WWW-Authenticate 中的 realm 字段传递。程序根据请求是否携带 Authorization 头区分两种情形:首次访问使用 realm “EEPW”;带了凭据却验证失败,则使用 realm “EEPW 密码错误,请重新登录”,浏览器会把这段文字显示在弹窗的消息区域。

与此同时,401 响应正文还放置了一个居中的“密码错误”页面。当用户点击弹窗的取消按钮时,浏览器会直接渲染这段正文,形成第二重提示。两种提示覆盖了“继续重试”和“放弃登录”两种用户行为。

2.5 页面设计

所有页面由 htmlPage(title, content) 统一生成。页面使用 flex 布局,使白色卡片在窗口中水平与垂直方向同时居中,卡片内部文字统一居中,并通过圆角与浅阴影提升观感。登录成功页包含三行内容:登录成功、欢迎进入EEPW世界、作者:keyboard007;其中作者行使用较小的灰色字体,与主标题形成层次,不喧宾夺主。

2.6 网络与升级设计

设备使用静态 IP,避免因 DHCP 租期到期或路由器重启导致地址变化。静态配置必须写在 WiFi.begin() 之前,否则不会生效。ArduinoOTA 在联网成功后启动,主循环持续调用其 handle 方法,使设备既能通过串口烧录,也能在局域网内无线升级。

第三章 程序流程图与代码编写步骤

3.1 启动流程

上电复位
  │
  ├─ Serial.begin(115200)
  ├─ WiFi.mode(WIFI_STA)
  ├─ WiFi.config(192.168.10.230, 网关, 掩码, DNS)
  ├─ WiFi.begin(SSID, 密码)
  │    ├─ 连接失败 → 打印提示 → 延时 1 秒 → ESP.restart()
  │    └─ 连接成功
  ├─ ArduinoOTA.begin()
  ├─ server.on("/", 处理函数)
  ├─ server.begin()
  └─ 串口打印访问地址 http://192.168.10.230/
 
主循环 loop()
  ├─ ArduinoOTA.handle()
  ├─ server.handleClient()
  └─ delay(2) 让出 CPU 给 WiFi 任务

3.2 请求处理流程

浏览器发起 GET /
  │
  ├─ 请求头中没有 Authorization
  │    └─ 返回 401,realm = EEPW,正文为“需要登录”页
  │         浏览器弹出登录框,用户输入账号密码
  │
  └─ 请求头中带有 Authorization
       ├─ 库解析 Basic 凭据,取出用户名
       ├─ 调用 credentialsHandler 查表
       │    ├─ 用户不存在或密码不符
       │    │    返回 401,realm = EEPW 密码错误,请重新登录
       │    │    弹窗显示错误提示,正文为“密码错误”页
       │    └─ 密码匹配
       │         返回 200,渲染“登录成功”页
       └─ 单次请求处理结束,等待下一次请求

3.3 代码编写步骤

(1)引入 WiFi.h、ESPmDNS.h、ArduinoOTA.h 与 WebServer.h 四个头文件。

(2)定义无线网络名称与密码,并创建监听 80 端口的 WebServer 对象。

(3)定义凭据结构体 credentials_t,建立账号数组,并用 sizeof 计算条目数。

(4)实现认证回调 credentialsHandler,遍历数组匹配用户名,返回密码指针或 NULL。

(5)实现页面生成函数 htmlPage,封装页面骨架、字体与居中样式。

(6)编写 setup 函数:初始化串口,切换 STA 模式,配置静态 IP,连接无线网络,判断连接结果,启动 OTA,注册根路径路由,启动服务并打印访问地址。

(7)编写 loop 函数:轮询 OTA 与 HTTP 客户端请求,并用 delay(2) 让出 CPU。

(8)编译烧录后,在浏览器中依次验证首次访问、密码错误、密码正确三种场景。

第四章 程序代码

完整源文件名为 06HttpAuthCallback.ino,全部代码如下:

#include <WiFi.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <WebServer.h>

const char *ssid = "航天科宇";
const char *password = "88888888";

WebServer server(80);

typedef struct credentials_t {
  String username;
  String password;
} credentials_t;

credentials_t passwdfile[] = {
  {"admin", "esp32"}, {"fred", "41234123"}, {"charlie", "sdfsd"}, {"alice", "vambdnkuhj"}, {"bob", "svcdbjhws12"},
};
const size_t N_CREDENTIALS = sizeof(passwdfile) / sizeof(credentials_t);

String *credentialsHandler(HTTPAuthMethod mode, String username, String params[]) {
  for (int i = 0; i < N_CREDENTIALS; i++) {
    if (username == passwdfile[i].username) {
      return new String(passwdfile[i].password);
    }
  }
  return NULL;
}

// 生成统一风格的页面,内容在窗口正中显示
String htmlPage(const String &title, const String &content) {
  String page = "<!DOCTYPE html><html><head><meta charset='utf-8'>";
  page += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
  page += "<title>";
  page += title;
  page += "</title>";
  page += "<style>html,body{height:100%;margin:0}";
  page += "body{display:flex;align-items:center;justify-content:center;background:#f5f6f7;";
  page += "font-family:-apple-system,'PingFang SC','Microsoft YaHei',sans-serif}";
  page += ".card{padding:32px 40px;background:#fff;border-radius:12px;";
  page += "box-shadow:0 4px 20px rgba(0,0,0,.08);text-align:center}";
  page += "h1{margin:0 0 12px;font-size:24px;color:#1a1a1a}";
  page += "p{margin:0;font-size:16px;color:#555}";
  page += ".author{margin-top:8px;font-size:14px;color:#888}</style></head>";
  page += "<body><div class='card'>";
  page += content;
  page += "</div></body></html>";
  return page;
}

void setup() {
  Serial.begin(115200);

  WiFi.mode(WIFI_STA);
  // 固定 IP:地址 192.168.10.230,网关 192.168.10.1,掩码 255.255.255.0
  IPAddress localIP(192, 168, 10, 230);
  IPAddress gateway(192, 168, 10, 1);
  IPAddress subnet(255, 255, 255, 0);
  IPAddress dns(192, 168, 10, 1);
  WiFi.config(localIP, gateway, subnet, dns);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Connect Failed! Rebooting...");
    delay(1000);
    ESP.restart();
  }
  ArduinoOTA.begin();

  server.on("/", []() {
    // 请求里带了 Authorization 头却认证失败,说明用户名或密码错误
    bool sentCredentials = server.hasHeader("Authorization");
    if (!server.authenticate(&credentialsHandler)) {
      if (sentCredentials) {
        server.requestAuthentication(
          BASIC_AUTH, "EEPW 密码错误,请重新登录",
          htmlPage("EEPW", "<h1>密码错误</h1><p>请重新输入正确的用户名和密码</p>"));
      } else {
        server.requestAuthentication(
          BASIC_AUTH, "EEPW",
          htmlPage("EEPW", "<h1>需要登录</h1><p>请输入用户名和密码</p>"));
      }
      return;
    }
    server.send(200, "text/html; charset=utf-8",
                htmlPage("EEPW", "<h1>登录成功</h1><p>欢迎进入EEPW世界</p>"
                                 "<p class='author'>作者:keyboard007</p>"));
  });
  server.begin();

  Serial.print("Open http://");
  Serial.print(WiFi.localIP());
  Serial.println("/ in your browser to see it working");
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();
  delay(2);  //allow the cpu to switch to other tasks
}

07-2 软件代码截图.png

07-1 网页截图.png

第五章 调试注意事项

5.1 编译与烧录

开发板必须选择与硬件对应的 FQBN,否则可能因 Flash 容量或分区表差异导致烧录后无法正常启动。本项目使用的是 esp32:esp32:m5stack_atoms3。

烧录前要关闭 Arduino IDE 的串口监视器,否则串口被占用,esptool 会报端口繁忙(PermissionError 13)并终止。串口监视器进程名通常为 serial-monitor,必要时可以先结束该进程再烧录。

编译与烧录过程中,工具链会在系统临时目录生成中间文件,若权限受限会导致编译中断,需要以具备写权限的账户运行。

5.2 网络与地址

静态 IP 必须与网关处于同一网段,并且是当前未被占用的地址。配错网关或掩码会导致设备虽然连上无线网络却无法访问,只能重新用 USB 烧录恢复,因此修改地址后应先用 ping 验证。

本项目对地址安全性做过一次实测:临时去掉静态配置改走 DHCP,设备仍然获得 192.168.10.230。这说明该地址要么位于路由器的可分配范围,要么路由器对本机 MAC 做了保留,两种情况短期内都不会冲突。为彻底排除隐患,仍建议在路由器后台确认地址池范围,或直接按 MAC 做静态绑定。

5.3 浏览器行为

浏览器会缓存 Basic 认证凭据,修改密码后可能在没有任何提示的情况下继续使用旧凭据,验证失败场景时应使用无痕窗口或更换访问路径。

realm 中的中文由浏览器负责渲染,个别浏览器可能显示为乱码。若出现该现象,把 realm 换成英文提示即可,页面正文中的中文不受影响。

5.4 中文与编码

页面必须声明 charset=utf-8,否则中文会显示为乱码;源文件本身也应保存为 UTF-8 编码,避免编辑后重新编译出现乱码。

5.5 安全性

Basic 认证只做 base64 编码而非加密,凭据可被抓包还原,仅适用于可信内网。密码硬编码在固件中,程序也没有失败次数限制和锁定机制,因此不适合直接当作公网登录系统使用。

5.6 无线升级

OTA 与串口烧录共用同一套升级机制,升级过程中不要断电;设备与电脑需要处于同一网段,否则网络端口无法被工具发现。

第六章 调试心得

第一,理解回调式认证的返回值语义是关键。返回密码指针而不是字符串,使“用户不存在”与“密码为空”两种状态得以区分;内存释放交给库处理,自己再 delete 反而会造成二次释放。这一点在写代码之前想清楚,可以省去很多排查时间。

第二,realm 可以当作提示通道使用,但要清楚它的边界:提示文字由浏览器渲染,页面正文在弹窗期间不可见。若对提示样式和交互有更高要求,正确做法是把认证换成自建登录表单与会话机制,而不是继续在响应头上做文章。

第三,推断不能代替实测。最初根据局域网扫描结果推断 DHCP 池为 100 到 199 段,认为 230 属于静态保留区;实际把静态配置去掉后设备依然拿到 230,说明推断有误。用一次真实测试推翻错误假设,比反复推理更高效。

第四,调试要分层。先用串口确认联网成功并看到 IP,再用 ping 与 ARP 确认链路可达、MAC 正确,最后用浏览器验证认证逻辑。这样出现问题时能迅速判断是网络层、服务层还是业务层的问题。

第五,改进方向已经比较明确。凭据可以迁移到 NVS 或加密分区;可以增加失败次数锁定与访问日志;对外网部署应改用 HTTPS;多页面场景可结合 Cookie 会话与分级权限。当前版本规模很小,但结构清晰,便于在此基础上继续扩展。




关键词: AtomS3R-M12     网页          认证    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]