Prechádzať zdrojové kódy

deploy: 域名映射反代配置 + HTTPS 下的 Secure Cookie

- deploy/nginx-yhm-shop-data.conf  80 跳 443、反向代理到 127.0.0.1:8787
- server.py 检测 X-Forwarded-Proto: https 时给会话 Cookie 加 Secure
- DEPLOY.md 补「映射到域名」四步:DNS、只监听本机、nginx、证书
海棉 1 týždeň pred
rodič
commit
c1bce44f2a

+ 60 - 5
store-ops/DEPLOY.md

@@ -133,14 +133,69 @@ sudo firewall-cmd --permanent --add-port=8787/tcp && sudo firewall-cmd --reload
 
 > 如果想用 80 端口,把 `PORT=80` 写进 service 文件,或者前面挂一个 nginx 反代。
 
-### 关于 HTTPS
+### 关于 HTTPS —— 映射到域名(必读)
 
-现在是纯 HTTP,**密码是明文传输的**。如果要走公网,建议:
+要让别人用 `https://yhm-shop-data.hzbeautybox.com/` 访问,需要四步,缺一不可:
 
-- 用 **Cloudflare** 免费套一层(域名 + 自动证书),源站指向你的服务器 IP
-- 或者用 nginx + Let's Encrypt(`certbot --nginx`)
+**第 1 步:DNS 加一条 A 记录**
 
-真要上公网前告诉我,我帮你配。
+找管 `hzbeautybox.com` 域名的人(IT / 运维)加:
+
+| 类型 | 主机记录 | 记录值 | TTL |
+|---|---|---|---|
+| A | `yhm-shop-data` | 服务器的**公网 IP** | 600 |
+
+验证生效:
+
+```bash
+dig +short yhm-shop-data.hzbeautybox.com    # 应该返回你的服务器 IP
+```
+
+> 如果服务器在内网、没有公网 IP,这条 A 记录要指向内网 DNS 能解析的地址,
+> 或者用内网 DNS 服务器单独加一条记录。
+
+**第 2 步:应用改成只监听本机**
+
+```bash
+# 改 systemd 的 ExecStart,加上 --local
+ExecStart=/bin/bash /opt/store-ops/start.sh --local
+sudo systemctl daemon-reload && sudo systemctl restart store-ops
+```
+
+这样 8787 端口不对公网开放,只能被 nginx 访问。
+
+**第 3 步:装 nginx 并反代**
+
+```bash
+sudo apt install -y nginx
+sudo cp /opt/store-ops/deploy/nginx-yhm-shop-data.conf /etc/nginx/conf.d/
+```
+
+**第 4 步:配证书(二选一)**
+
+```bash
+# A. 免费证书(推荐,需公网能访问 80 端口)
+sudo apt install -y certbot python3-certbot-nginx
+sudo certbot --nginx -d yhm-shop-data.hzbeautybox.com
+
+# B. 公司已有证书:把 .crt / .key 放到 /etc/nginx/ssl/,文件名对上即可
+```
+
+然后:
+
+```bash
+sudo nginx -t && sudo systemctl reload nginx
+```
+
+浏览器打开 `https://yhm-shop-data.hzbeautybox.com/` 应该能看到登录页。
+
+> 应用检测到 `X-Forwarded-Proto: https` 时会自动给会话 Cookie 加 `Secure` 标记。
+
+**安全组 / 防火墙要放行 80 和 443**(8787 不用对外了):
+
+```bash
+sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw delete allow 8787/tcp
+```
 
 ---
 

+ 16 - 3
store-ops/backend/server.py

@@ -394,6 +394,20 @@ class Handler(BaseHTTPRequestHandler):
     def _err(self, msg, code=400):
         self._json({"ok": False, "error": msg}, code)
 
+    def _is_https(self):
+        """判断这次请求是不是 HTTPS(可能被 nginx 反代,所以看 X-Forwarded-Proto)"""
+        if (self.headers.get("X-Forwarded-Proto") or "").lower() == "https":
+            return True
+        try:
+            import ssl
+            return isinstance(self.connection, ssl.SSLSocket)
+        except Exception:
+            return False
+
+    def _cookie(self, value, max_age):
+        return "sid=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=Lax%s" % (
+            value, max_age, "; Secure" if self._is_https() else "")
+
     def _cookies(self):
         out = {}
         for part in (self.headers.get("Cookie") or "").split(";"):
@@ -468,8 +482,7 @@ class Handler(BaseHTTPRequestHandler):
                               "role": u["role"],
                               "display_name": u["display_name"]}).encode()
         return self._send(200, payload, "application/json; charset=utf-8", {
-            "Set-Cookie": "sid=%s; Path=/; Max-Age=%d; HttpOnly; SameSite=Lax"
-                          % (token, SESSION_DAYS * 86400)})
+            "Set-Cookie": self._cookie(token, SESSION_DAYS * 86400)})
 
     def _user_create(self):
         try:
@@ -714,7 +727,7 @@ class Handler(BaseHTTPRequestHandler):
                     db.delete_session(self.sid)
                 return self._send(200, json.dumps({"ok": True}).encode(), 
                                   "application/json; charset=utf-8",
-                                  {"Set-Cookie": "sid=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax"})
+                                  {"Set-Cookie": self._cookie("", 0)})
             if u.path == "/api/users":
                 return self._user_create()
             if u.path == "/api/import":

+ 78 - 0
store-ops/deploy/nginx-yhm-shop-data.conf

@@ -0,0 +1,78 @@
+# 门店检测工作台 —— nginx 反向代理配置
+#
+# 用途:把 https://yhm-shop-data.hzbeautybox.com/ 转发到本机的 127.0.0.1:8787
+#
+# 安装:
+#   sudo cp deploy/nginx-yhm-shop-data.conf /etc/nginx/conf.d/
+#   sudo nginx -t && sudo systemctl reload nginx
+#
+# 证书(二选一):
+#   A. Let's Encrypt 免费证书
+#      sudo certbot --nginx -d yhm-shop-data.hzbeautybox.com
+#      (certbot 会自动改这个文件的 443 部分,不用手写 ssl 参数)
+#   B. 公司已有证书 → 把下面 ssl_certificate 两行改成实际路径
+#
+# 注意:应用要以 --local 启动,只监听 127.0.0.1,不直接暴露 8787
+
+# ---------- 80:全部跳转到 HTTPS ----------
+server {
+    listen 80;
+    listen [::]:80;
+    server_name yhm-shop-data.hzbeautybox.com;
+
+    # Let's Encrypt 续期验证需要这一条
+    location /.well-known/acme-challenge/ {
+        root /var/www/html;
+    }
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+# ---------- 443:正式服务 ----------
+server {
+    listen 443 ssl;
+    listen [::]:443 ssl;
+    http2 on;
+    server_name yhm-shop-data.hzbeautybox.com;
+
+    # ---- 证书(改用 Let's Encrypt 的话把这两行替换成 certbot 生成的路径)----
+    ssl_certificate     /etc/nginx/ssl/yhm-shop-data.crt;
+    ssl_certificate_key /etc/nginx/ssl/yhm-shop-data.key;
+    # Let's Encrypt 默认路径:
+    # ssl_certificate     /etc/letsencrypt/live/yhm-shop-data.hzbeautybox.com/fullchain.pem;
+    # ssl_certificate_key /etc/letsencrypt/live/yhm-shop-data.hzbeautybox.com/privkey.pem;
+
+    ssl_protocols       TLSv1.2 TLSv1.3;
+    ssl_ciphers         HIGH:!aNULL:!MD5;
+    ssl_session_cache   shared:SSL:10m;
+    ssl_session_timeout 1d;
+
+    # 上传 Excel 用,文件一般几百 KB,留足余量
+    client_max_body_size 30m;
+
+    # 安全响应头
+    add_header X-Content-Type-Options nosniff always;
+    add_header X-Frame-Options SAMEORIGIN always;
+    add_header Referrer-Policy no-referrer-when-downgrade always;
+
+    location / {
+        proxy_pass http://127.0.0.1:8787;
+        proxy_http_version 1.1;
+
+        # 让后端知道真实来源(会话记录、日志、Secure Cookie 判断都靠这几个)
+        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_connect_timeout 30s;
+        proxy_send_timeout    600s;
+        proxy_read_timeout    600s;
+
+        # 导出大文件时不要缓冲到内存
+        proxy_buffering off;
+    }
+}