什么是无感登录

用户登录博客(飞书或密码)后,点击 OpenClaw 入口直接进入,无需二次输入密码。原理是 Cookie 桥接:

Django 写 cookie → Nginx 读 cookie 转 HTTP Header → Gateway 信任 Header → 认证通过

架构图

                  浏览器
                    │
          Cookie: openclaw_user=kivenz
                    │
                    ▼
              Nginx (:443)
                    │
         读 $cookie_openclaw_user
         注入 X-Forwarded-User: kivenz
                    │
                    ▼
         OpenClaw Gateway (:18789)
          trusted-proxy 模式
         信任 127.0.0.1 / 172.18.0.1
                    │
                    ▼
              认证为用户 kivenz

三层配置

第一层:Django(写 Cookie)

console/views.py — ConsoleIndexView 在用户访问控制台首页时写入 cookie:

response.set_cookie(
    "openclaw_user", request.user.username,
    httponly=True,           # JS 不可读,防 XSS
    samesite="Strict",       # 仅同站请求携带,防 CSRF
    max_age=86400,           # 24 小时有效
)

settings.py — 环境变量:

OPENCLAW_GATEWAY_URL = os.environ["OPENCLAW_GATEWAY_URL"]    # Gateway 外部地址
OPENCLAW_TOKEN = os.environ["OPENCLAW_TOKEN"]                # Gateway 管理员 Token
OPENCLAW_INTERNAL_URL = os.environ.get("OPENCLAW_INTERNAL_URL", "http://127.0.0.1:18789")

.env 中配置:

OPENCLAW_GATEWAY_URL=https://openclaw-gateway.example.com/login
OPENCLAW_TOKEN=your-token-here
OPENCLAW_INTERNAL_URL=http://127.0.0.1:18789   # 可选,默认就是这个

console/views.py — OpenClawProxyView 开发环境下作为兜底代理:

headers["X-Forwarded-User"] = request.user.username
# 转发请求到 Gateway
resp = requests.request(method=request.method, url=target, headers=headers, ...)

第二层:Nginx(Cookie → Header)

deploy/nginx_openclaw.conf:

location /console/openclaw {
    proxy_pass http://127.0.0.1:18789;
​
    # 核心:把 cookie 值注入到 HTTP 头
    proxy_set_header X-Forwarded-User $cookie_openclaw_user;
​
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
​
    proxy_read_timeout 86400s;
    proxy_buffering off;
    client_max_body_size 100m;
}

关键行:proxy_set_header X-Forwarded-User $cookie_openclaw_user;

  • $cookie_openclaw_user 是 Nginx 内置变量,自动读取请求中名为 openclaw_user 的 cookie 值
  • proxy_set_header X-Forwarded-User 将该值作为 HTTP 头注入到转发请求中

部署到宝塔:把这个文件复制到 /www/server/panel/vhost/nginx/extension/kivenz_blog/openclaw.conf,然后在站点配置中 include。

第三层:Gateway(信任 Header)

openclaw.json:

{
  "gateway": {
    "auth": {
      "mode": "trusted-proxy",
      "trustedProxy": {
        "userHeader": "x-forwarded-user"
      }
    },
    "controlUi": {
      "basePath": "/console/openclaw"
    },
    "trustedProxies": ["127.0.0.1", "172.18.0.1"]
  }
}
  • mode: "trusted-proxy" —— Gateway 不在自身做认证,信任上游代理
  • userHeader: "x-forwarded-user" —— 从哪个 HTTP 头读取用户名
  • trustedProxies —— 安全关键:只有来自这些 IP 的请求才会被信任。外部 IP 伪造 X-Forwarded-User 头会被忽略

完整请求流程

生产环境(Nginx 在中间)

1. 用户登录博客
   → Django 认证通过 → 写 openclaw_user=kivenz cookie
​
2. 用户点击 OpenClaw 入口
   → 浏览器发 GET /console/openclaw/ + Cookie: openclaw_user=kivenz
​
3. Nginx 收到请求
   → 读 cookie → 转 HTTP Header: X-Forwarded-User: kivenz
   → 代理到 127.0.0.1:18789
​
4. Gateway 收到请求
   → 检查来源 IP 在 trustedProxies 中 (127.0.0.1 ✓)
   → 读 X-Forwarded-User: kivenz
   → 认证为用户 kivenz
   → 返回 OpenClaw 控制台

开发环境(Django 直接代理)

1. 同上
​
2. 同上
​
3. Django OpenClawProxyView
   → 从 request.user.username 拿用户名
   → 注入 X-Forwarded-User: kivenz header
   → requests.get("http://127.0.0.1:18789", headers=...)
​
4. Gateway 同上

安全机制

层级机制防护
Cookiehttponly=TrueJS 无法读取,防 XSS 窃取
Cookiesamesite="Strict"跨站请求不带 cookie,防 CSRF
Cookiemax_age=8640024 小时过期,减少泄露窗口
DjangoLoginRequiredMixin + is_staff只有管理员能访问 OpenClaw
GatewaytrustedProxies 白名单外部 IP 伪造 X-Forwarded-User 无效
Nginx仅转发 $cookie_openclaw_user不接受客户端传入的 X-Forwarded-User

常见问题

Q: Cookie 过期了怎么办?

A: openclaw_user cookie 有效期 24 小时。过期后重新访问一次 /console/ 会刷新 cookie(无需重新登录,只要 Django session 还在)。

Q: 为什么有时要重新登录?

A: Django session 默认 3 天。如果 session 也过期,需要重新登录(飞书或密码)。

Q: 能在公网直接访问 Gateway 绕过认证吗?

A: 不能。Gateway 在 trusted-proxy 模式下,只要 X-Forwarded-User 头不是来自白名单 IP,就会被忽略。公网请求到 Gateway 会因为没有有效身份信息而被拒绝。

Q: 本地开发怎么测?

A: 启动 Gateway → 启动 Django python manage.py runserver → 访问 http://127.0.0.1:8000/console/login/ 登录 → 然后访问 /console/openclaw/。Django 的 OpenClawProxyView 会自动处理代理。