Python HTTP服务安全认证:Basic Auth三种实现方案

发布时间:2026/9/9 12:33:43

Python HTTP服务安全认证:Basic Auth三种实现方案 1. 先搞清楚我们到底在给什么加认证如果你自己搭过 Python 的临时 HTTP 服务一定见过这条命令python -m http.server 8000它能把当前目录变成一个可浏览下载的网站局域网里打开http://192.168.x.x:8000就能访问。问题也出在这里这个服务默认不设防只要有人扫到端口目录里所有文件都能被拉走。之前我用它给别人发资料结果没过多久就在日志里看到一堆来自陌生地址的探测请求才意识到内网也不是绝对安全。所以我养成了一个习惯不管多临时只要服务要开放给不是自己一个人用就先加上最简单的用户名密码认证。认证方式不需要复杂HTTP 协议自带的 Basic Auth 就够用。浏览器会自己弹登录框用户不需要记住任何额外链接也不需要配置客户端比“在 URL 里塞 token”这种野路子正规得多。这篇文章我会用三种方案实现同一件事基于标准库http.server的SimpleHTTPRequestHandler加认证适合静态文件共享基于BaseHTTPRequestHandler自定义请求处理适合你要自己写路由的 API 服务基于 WSGI 中间件配合waitress部署适合你打算把服务跑稳一点、以后要接正式环境的情况。你看完以后按自己的场景选一种抄走就行不用三份都读。我会把代码里的每个关键判断都解释清楚顺便把我踩过的坑一并说掉。1.1 默认的 http.server 为什么不行http.server是 Python 标准库内置的模块很多入门教程都拿它演示“一行代码开网站”。它底层调用socketserver默认监听0.0.0.0也就是说它会绑定本机所有网卡地址。在物理服务器上这等于所有能访问到这个主机的用户都能看到服务在个人电脑上哪怕你只连了公司局域网也会被同一个 WiFi 下的人扫到。最直接的问题是没有认证。SimpleHTTPRequestHandler只会忠实地把磁盘文件映射成 URL任何人发起 GET 请求都能拿到内容。它对HEAD、GET、POST的处理也很简单HEAD只返回头信息GET返回文件内容POST默认直接报 501。这种设计本来就不是给生产环境用的它连基本的访问控制接口都没暴露出来。并不是说它没用而是说你应该在它外面加一层“门禁”。加认证的本质是在请求真正进入目录映射逻辑之前先检查Authorization请求头里的凭据是否正确。只要校验没通过直接返回 401并告诉浏览器去弹登录框。1.2 Basic Auth 的原理和适用范围Basic Auth 是 HTTP/1.0 时代就定下的认证方式。它的流程非常简单客户端第一次请求时通常没有Authorization头服务端发现没有这个头返回401 Unauthorized并在响应头里带上WWW-Authenticate: Basic realmxxx浏览器的登录框读取 realm 作为提示文字用户输入用户名密码浏览器用冒号把用户名和密码拼成username:password再做一次 Base64 编码放到请求头里发回来服务端解出这一段比对用户名密码通过就继续处理请求不通过就再次返回 401。这里要强调一点Base64 不是加密它只是一种可逆的编码形式。把admin:123456编码成一串字符随便找个在线工具就能倒推回来。所以 Basic Auth 只在局域网、内网或者有 HTTPS 加密的前提下算“能用”。它在公网上裸奔等于把密码明文交给网络里的任何中间设备。1.3 三种方案的适用场景对比我先把三种方案的适用场景列成表格方便你快速定位。方案依赖适合场景代码量维护成本方案一SimpleHTTPRequestHandler标准库共享静态文件、临时下载页少低方案二BaseHTTPRequestHandler标准库自写 API、自定义路由、网关式服务中中方案三WSGI waitresswaitress、werkzeug正式部署、多线程、后续要接 WSGI 应用中中如果只是给前端打包出来的页面做本地预览方案一足够如果是给用户提供一个带登录态的接口服务方案二更好如果你已经习惯了 Flask 那种app.route的写法又不想被框架绑定方案三可以让你用纯 WSGI 方式搭服务后面换成 Gunicorn 也没压力。2. 方案一给静态文件服务器套上认证方案一改造的是http.server里最常用的SimpleHTTPRequestHandler不需要引入任何第三方库适合python -m http.server这种一键启动场景。我实际的使用场景一般是临时把某个目录开放给同事下载资料、给项目组共享构建产物、或者在自己电脑上预览一个静态站点。这类需求最看重的就是“改得少、跑得快”。2.1 手写 Authorization 校验函数我们需要一个函数从请求头里取出Authorization判断它是不是 Basic Auth并把 Base64 解码后的用户名密码拆出来比对。import base64 USERNAME admin PASSWORD secret123 def is_authorized(headers): auth headers.get(Authorization, ) if not auth.startswith(Basic ): return False try: payload base64.b64decode(auth[6:]).decode(utf-8) username, _, password payload.partition(:) except Exception: return False return username USERNAME and password PASSWORD我特意加了try/except因为你不能假设客户端每次发的都是合法 Base64。有人用脚本扫描时Authorization头可能是一些乱写的值比如Basic !或者直接把 Token 放在 Bearer 头里。一旦base64.b64decode解不出来我们的代码不能像没看见一样继续往下面走而是统一当作未认证处理。partition(:)返回一个三元组这里只需要用户名和密码。注意用户名里不能有冒号因为协议约定第一个冒号是分隔符密码里如果有冒号反而没事partition只按第一次出现的位置拆分。2.2 用 send_head 统一拦截所有请求接下来新建一个请求处理类继承SimpleHTTPRequestHandler并重写send_head。为什么要重写这个方法而不是do_GET因为SimpleHTTPRequestHandler的do_GET和do_HEAD最后都会调用send_head去构造响应所以在这里拦截等于一次性覆盖了 GET 和 HEAD 两种请求。from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer class AuthHandler(SimpleHTTPRequestHandler): def send_head(self): if not is_authorized(self.headers): self.send_response(401) self.send_header( WWW-Authenticate, Basic realmprivate-files ) self.send_header(Content-Length, 0) self.end_headers() return None return super().send_head()如果is_authorized返回 False我们直接返回 401 和一个WWW-Authenticate头。realm是登录框上显示的提示信息浏览器会根据它缓存同一套凭据。如果你只有一个服务这个值写什么都行如果你在同一站点可能部署多个独立认证区域realm 要小心区分否则浏览器会混用凭据。返回None是合法的因为do_GET拿到send_head的返回值之后只有返回值是真值时才继续写文件内容否则就直接结束连接。写一个空 body 的 401 响应既不会让恶意请求拿走任何文件也不会浪费带宽。最后加上启动入口def main(): server ThreadingHTTPServer((0.0.0.0, 8000), AuthHandler) print(serve on 0.0.0.0:8000) server.serve_forever() if __name__ __main__: main()我用ThreadingHTTPServer而不是HTTPServer是因为它每来一个连接就开一个线程处理遇到多个浏览器同时下载文件时不会互相阻塞。Python 3.7 之后都内置了这个类不需要额外装线程库。如果你只是在自己电脑上临时跑一下用哪个区别不大。2.3 用命令行参数动态配置用户名密码把用户名密码写死在代码里有个问题换一次密码就要改代码。对临时服务来说这其实挺烦的。所以我会在启动脚本里加上argparseimport argparse parser argparse.ArgumentParser() parser.add_argument(--username, defaultadmin) parser.add_argument(--password, defaultsecret123) parser.add_argument(--port, typeint, default8000) parser.add_argument(--directory, default.) args parser.parse_args()启动时就可以这样用python auth_server.py --username alice --password pss --port 9000 --directory ./share这里有个容易踩的坑千万不要把密码直接写在命令行历史里尤其当你在共享机器上操作时。临时用用可以正式一点的话把密码放到环境变量或者本地配置文件里脚本里读取os.environ的值这样命令历史里就不会留下明文密码。用环境变量还有个好处后面配合 Docker 或 systemd 部署时不用改代码只改环境变量就能换密码。3. 方案二BaseHTTPRequestHandler 打造自定义 API 认证如果你不只是想共享文件而是想提供一个带认证的 JSON 接口那么SimpleHTTPRequestHandler就不够灵活了。它把目录映射逻辑写死了你想加一个/api/status路由、想区分 GET 和 POST、想返回 JSON 而不是文件内容都得费劲绕过去。这时候应该直接继承BaseHTTPRequestHandler把请求处理的逻辑握在自己手里。3.1 一个带路由的最小认证服务器继承BaseHTTPRequestHandler之后你需要自己实现do_GET、do_POST等方法。认证检查可以写成一个私有方法在所有入口统一调用。下面是去掉文件服务、只返回 JSON 的最小示例import base64 import json from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer USERNAME alice PASSWORD pssw0rd class AuthAPIHandler(BaseHTTPRequestHandler): def _check_auth(self): auth self.headers.get(Authorization, ) if not auth.startswith(Basic ): return False try: raw base64.b64decode(auth[6:]).decode(utf-8) user, _, pwd raw.partition(:) except Exception: return False return user USERNAME and pwd PASSWORD def _send_401(self): self.send_response(401) self.send_header(WWW-Authenticate, Basic realmapi) self.send_header(Content-Type, application/json; charsetutf-8) body json.dumps({error: authentication required}).encode(utf-8) self.send_header(Content-Length, str(len(body))) self.end_headers() self.wfile.write(body) def _send_json(self, data, status200): body json.dumps(data).encode(utf-8) self.send_response(status) self.send_header(Content-Type, application/json; charsetutf-8) self.send_header(Content-Length, str(len(body))) self.end_headers() self.wfile.write(body) def do_GET(self): if not self._check_auth(): self._send_401() return if self.path /api/ping: self._send_json({message: pong}) elif self.path /api/time: self._send_json({now: 2025-01-01T00:00:00}) else: self._send_json({error: not found}, status404) def log_message(self, fmt, *args): print(f{self.address_string()} - {fmt % args})注意我在 401 响应里设置了Content-Length为 JSON body 的长度而不是直接调send_error。send_error会返回 HTML 错误页对前后端分离的项目不太友好。如果你要做一个纯 JSON API最好统一用_send_json这种方式控制响应格式。前端拿到 401 后只要看到响应头里有WWW-Authenticate就能触发浏览器的登录弹窗。3.2 给接口加 POST 和错误处理很多入门项目只实现了do_GET结果前端用fetch发POST请求时直接得到 501。继承BaseHTTPRequestHandler后所有方法的默认实现都是返回 501所以如果你不实现do_POST它就会报“Unsupported method”。如果你要接收 JSON记得手动读取并解析 body。class AuthAPIHandler(BaseHTTPRequestHandler): def do_POST(self): if not self._check_auth(): self._send_401() return try: length int(self.headers.get(Content-Length, 0)) payload self.rfile.read(length) data json.loads(payload.decode(utf-8)) except Exception: self._send_json({error: bad json}, status400) return if self.path /api/submit: self._send_json({received: data}) else: self._send_json({error: not found}, status404)需要留意的是这里的Content-Length是用字符串形式接收的布尔判断时0是 True所以要用int()转换。另外self.rfile.read(length)若客户端没有正确的 Content-Length 头可能
延伸阅读

更多相关文章

2026/9/9 12:33:43

Python等值线图绘制实战:从插值到出图全流程

简介:这是一份基于D3.js实现等值线图、等高线图与等值云图的Web可视化示例,适合前端开发者、GIS爱好者及需要在地理或气象数据展示中应用插值算法的工程师参考。资源通过反距离加权法(IDW)对离散点数据进行空间插值,再…

2026/9/9 12:33:43

技能体系构建指南:从技能树到刻意练习的完整方法论

1. 简历上的Skills:一个被严重低估的词语1.1 从一次面试说起前几年我做技术面试官的时候,遇到过不少简历上Skills栏写得满满当当的候选人。从Python到Photoshop,从项目管理到视频剪辑,长长一排,乍一看是个全能选手。但…

2026/9/9 13:39:18

软件测试工程师转型量子计算:2026认证路径与实战指南

开头先聊个现象。我身边不少做软件测试的朋友,这两年聊天话题越来越集中在“测试这行还能干多久”上。业务功能测试需求确实在收缩,AI辅助编码又在改写整个研发链条,岗位的护城河被一层层削平。但与此同时,另一个方向正静悄悄地打…

2026/9/9 13:39:18

能源行业采购新生态:打印耗材如何撬动非生产性物资数字化变革

前几天刷到一条消息:格之格出现在京东举办的能源行业采购主题会议上,主题直指“能源行业采购新生态”。乍一看,这不过是品牌方参加了一次平台活动,但仔细琢磨,这个话题背后藏着不少值得展开的东西——一个是深耕打印耗…

2026/9/9 13:39:18

AI Agent连续工作时长惊人,3.1倍人力背后靠什么支撑?

3.1倍,这个数字在最近AI Agent圈子里刷屏了。Rohan Paul对OpenAI研究组织相关分享的二次解读,核心就一句话:在标准研发任务基准上,Agent的连续工作时长已经能做到人力的3.1倍。我第一反应是怀疑,毕竟“AI替代人力”的标…

2026/9/9 13:39:18

Excel数据批量填充Word模板:VBA自动化实战指南

简介:面向办公自动化与批量文档生成需求,这份源码工具聚焦如何用VBA将Excel等数据源批量填充至Word模板,帮助行政、财务、人事等岗位摆脱重复手工替换占位符的低效操作。资源包共4个文件,压缩包仅21KB,包含doc示例模板…

2026/9/9 13:39:18

OpenClaw测试提效实践:AI Agent驱动的用例设计与缺陷分析

最近这两个月,我们测试组的工作方式发生了挺大变化。起因是我把OpenClaw——社区里都叫它“AI小龙虾”的开源Agent框架——引入了日常测试流程。原来要花一上午梳理的用例设计,现在交给它配合大模型跑一轮,四十分钟能拿到可评审的初稿&#x…

2026/9/9 13:34:17

构建生产级Agent基础设施:hermes-agent的设计与实践

市面上的Agent框架不少,但真正拿到生产环境里用的时候,问题一堆:要么工具调用不可控,要么会话状态乱七八糟,要么出了问题根本没法排查。我自己在做一个内部客服机器人项目的时候,被这些问题折磨得够呛&…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/8 7:15:10

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”,或者让大模型自己调一版机械臂的运动轨迹,这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现,模型不缺智商,缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我,他想转行学AI,但打开招聘网站一看直接傻眼:机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词,好像每个都会一点,又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程,而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环,到能够承载生产流量的AI引擎,中间差的不是代码量,而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码