背景
这次在一台已经安装宝塔面板的 Linux 服务器上部署一个 Next.js 项目。
项目本身是:
部署完成后,前台页面可以打开,但是访问后台时浏览器会弹出原生登录框,并提示“您与此网站的连接不是私密连接”。
这次排查的目标很明确:先把项目正常部署起来,然后关闭 Basic Auth,避免浏览器再弹原生认证框。
一、最开始遇到的问题是什么
当时访问的是:
http://<IP>:8080/admin
浏览器会弹出 Basic Auth 登录框,并提示连接不私密。
这里要先分清楚两件事:
-
这不是程序报错
-
这是浏览器对“HTTP 明文连接下输入账号密码”的安全提醒
只要继续使用:
HTTP + Basic Auth
这个提示就很难消除。
二、项目原来的鉴权是怎么做的
项目里用的是 Next.js middleware,大致逻辑如下:
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
const authRealm = 'Portfolio Admin'
const unauthorizedResponse = () =>
new NextResponse('Authentication required', {
status: 401,
headers: {
'WWW-Authenticate': `Basic realm="${authRealm}", charset="UTF-8"`,
},
})
export function middleware(request: NextRequest) {
const username = process.env.ADMIN_USERNAME
const password = process.env.ADMIN_PASSWORD
// 读取 authorization 头并校验
// 校验失败则返回 401
}
export const config = {
matcher: [
'/admin',
'/admin/:path*',
'/api/content',
'/api/content/:path*',
'/api/upload',
'/api/upload/:path*',
'/api/upload-logo',
'/api/upload-logo/:path*',
],
}
从这里可以看出:
-
/admin受 Basic Auth 保护
-
内容保存接口和上传接口也一起受保护
所以只要这个中间件还生效,访问/admin就一定会弹浏览器原生认证框。
三、第一次尝试:想通过环境变量关闭 Basic Auth
一开始我的想法是,不直接删掉鉴权逻辑,而是加一个开关,例如:
const authEnabled = process.env.ADMIN_AUTH_ENABLED !== 'false'
if (!authEnabled) {
return NextResponse.next()
}
然后在生产环境变量里加:
ADMIN_AUTH_ENABLED=false
这个思路本身没有问题,优点也很明显:
-
改动小
-
可回滚
-
后续想重新开启鉴权时,只需要改环境变量
但是实际上,容器重建后访问/admin仍然返回401,说明 Basic Auth 依旧在生效。
四、为什么环境变量方案没有达到预期
这一步最麻烦的地方在于:源码看起来已经改对了,环境变量也已经写进.env.production了,但运行结果不对。
当时做了几轮确认:
-
确认服务器源码目录里的src/middleware.ts已经包含ADMIN_AUTH_ENABLED
-
确认.env.production已经有:
ADMIN_AUTH_ENABLED=false
-
确认容器确实已经重建
-
确认容器内ADMIN_USERNAME和ADMIN_PASSWORD仍然存在
-
继续检查.next里的编译产物,确认中间件打包后的代码里也确实出现了ADMIN_AUTH_ENABLED
到这里说明一个问题:这不是“代码没同步上去”,也不是“环境变量没传进去”,而是这个关闭开关在实际构建和运行链路里没有按预期生效。
五、第二次尝试:直接移除 Basic Auth 逻辑
因为这次的目标不是“做一个长期可配置的后台认证方案”,而是“先把浏览器原生 Basic Auth 登录框关掉”,所以后面改成了更直接的处理方式:
把middleware.ts改成无条件放行。
最终改成这样:
import { NextResponse } from 'next/server'
export function middleware() {
return NextResponse.next()
}
export const config = {
matcher: [
'/admin',
'/admin/:path*',
'/api/content',
'/api/content/:path*',
'/api/upload',
'/api/upload/:path*',
'/api/upload-logo',
'/api/upload-logo/:path*',
],
}
这里有一个细节:
-
matcher还保留着
-
只是这些路径命中后不再做认证,而是直接放行
这样做的好处是:
-
不影响中间件匹配范围
-
不需要额外改页面或接口代码
-
行为足够直接,结果容易验证
最后重新构建镜像并启动容器后正常。
六、最终结论
一句话总结:
浏览器里的 Basic Auth 登录框,本质上是服务端返回了401 + WWW-Authenticate;只要中间件不再返回这个响应,弹框自然就消失了。
当然,这只是这次的临时方案,如果要保证后台安全则需要留下认证。
来源:https://www.cnblogs.com/Higurashi-kagome/p/19882396 |