给内部系统的 Nginx 再加一道门:Basic Auth 配置记录
最近有个内部系统需要放到公网入口上,但它本身已经有一套登录。最开始我觉得这就够了,后来还是想在 Nginx 前面再加一层 Basic Auth。
原因不完全是担心有人猜到应用账号密码。更现实一点:只要登录页能直接打开,扫描器就能拿到网页标题、Logo、前端静态资源、接口路径,甚至从报错和构建产物里猜出技术栈。对于只给内部人员用的系统,这些内容其实没必要先暴露出去。
所以最后的思路是:先让 Nginx 拦住入口,过了 Basic Auth 才能看到应用自己的登录页;进入应用后,还是照常走原来的账号、权限和验证码。两层认证解决的不是同一个问题。
配置
这个系统是一个前端单页应用,构建结果在 /home/position-tree/dist,Nginx 配置最后是这样的:
nginx
server {
listen 82;
root /home/position-tree/dist;
index index.html;
auth_basic "仅限内部许可访问";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
try_files $uri $uri/ /index.html;
}
}
auth_basic 打开 HTTP Basic Authentication。后面的文字是认证域(realm),浏览器弹出用户名密码框时会显示它。
auth_basic_user_file 指向用户文件。这个文件保存的是用户名和密码哈希,Nginx 每次收到请求时都用它校验请求里的凭据。
配置放在 server 级别比较省心:首页、Logo、JS、CSS 和通过 Nginx 暴露出来的其他路径都会先经过认证。这样未认证请求拿不到 index.html,也就拿不到网页标题和页面内容。
try_files $uri $uri/ /index.html; 和认证没有直接关系,它只是 SPA 的路由回退。访问一个没有对应真实文件的前端路由时,Nginx 最终还是返回 index.html,交给前端路由处理。
浏览器到底发了什么
第一次访问受保护路径时,浏览器还没有认证信息。Nginx 不会把页面返回给它,而是返回:
http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="仅限内部许可访问"
浏览器识别到 WWW-Authenticate 后,就会弹出原生的用户名密码输入框。
用户输入凭据后,浏览器会在后续请求中带上:
http
Authorization: Basic YWRtaW46c2VjcmV0
这里的 YWRtaW46c2VjcmV0 是 admin:secret 的 Base64 编码。它不是加密,拿到请求的人可以很容易还原用户名和密码。
因此 Basic Auth 一定要配 HTTPS。HTTP 明文传输时,这层认证几乎等于把密码暴露在链路上。
Nginx 读到 Authorization 后,会从 .htpasswd 里找对应用户名的密码哈希并校验。通过就继续执行 try_files 或 proxy_pass;失败仍然返回 401,应用服务本身根本收不到这次请求。
两层认证分别保护什么
Nginx 的 Basic Auth 更像入口门禁。它决定的是“请求能不能先碰到这个系统”。
应用自己的登录、SSO、验证码和 RBAC 则决定“这个用户是谁,以及他进来后能做什么”。
Basic Auth 不适合承担用户权限、操作审计、单点登录和 MFA 这些职责。很多时候入口账号还会被几个内部人员共用,单靠它甚至无法区分具体操作人。所以应用原有的认证不能因为加了这一层就下掉。
它的价值是把没有入口凭据的请求挡在应用外面,减少页面信息和攻击面的暴露。
重新生成 .htpasswd
Debian/Ubuntu 上需要先装 htpasswd 命令:
bash
sudo apt update
sudo apt install apache2-utils
第一次创建文件:
bash
sudo htpasswd -c /etc/nginx/.htpasswd admin
命令会要求输入两次密码。这里的 -c 是 create,会创建新文件;如果文件已经存在,它会直接覆盖旧内容。这个参数最容易踩坑,新增用户时千万别带。
给已有文件新增用户,或者重置某个用户密码:
bash
sudo htpasswd /etc/nginx/.htpasswd zhangsan
现代环境里可以明确使用 bcrypt:
bash
sudo htpasswd -B /etc/nginx/.htpasswd zhangsan
文件内容大概是这样:
text
zhangsan:$2y$05$...
冒号前是用户名,后面是密码哈希,不是明文密码。密码哈希仍然需要保护:它泄露后虽然不能直接登录,但弱密码仍然有被离线爆破的风险。
可以顺手限制文件权限:
bash
sudo chown root:root /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
权限不要图省事开成 777。如果 Nginx 读不到文件,应该先看错误日志,并确认 worker 运行用户是 www-data、nginx 还是其他用户。
生效前先检查
配置改完先检查语法:
bash
sudo nginx -t
确认没问题后再重载:
bash
sudo systemctl reload nginx
不带凭据访问时可以验证是否返回 401:
bash
curl -I http://127.0.0.1:82/
带凭据验证:
bash
curl -I -u zhangsan:你的密码 http://127.0.0.1:82/
如果要让监控系统访问健康检查接口,也可以只对那一个路径关闭认证:
nginx
location = /health {
auth_basic off;
return 200 "ok\n";
}
这类例外要尽量少。尤其是 SPA,不要只保护首页却把静态资源或接口单独放开,不然前面加的这层门禁就只剩一个登录弹窗了。
这层门不能让服务完全隐身
加了 Basic Auth 以后,扫描器看不到页面内容,但仍然能发现域名、端口,以及服务返回了 401 Unauthorized。认证域里的文字也会出现在响应头里,所以别在 realm 里写系统名称、项目代号之类的信息。
对敏感程度更高的系统,Basic Auth 应该和 VPN、零信任网关、IP 白名单、防火墙一起用。它适合做外层防御,不是把公网服务变成内网服务的魔法。
这次加它主要不是为了替代原来的登录,而是觉得内部系统的 Logo、标题和登录页本来就不该直接送给扫描器。Nginx 在应用前面先拦一道,成本很低,也比较符合这个场景。