# -*- coding: utf-8 -*- r'''Halo 登录(第三版):处理 CSRF,然后发文章。 == 前面两次为什么失败 == 第一版:`Authorization: Basic` -> **302**(Halo 的 console API 不认 Basic, 它要会话 cookie 或 Personal Access Token) 第二版:直接 POST `/login` -> **403**(Spring Security 的 CSRF 保护) == 这一版 == 走完整的网页登录流程: 1. GET /login -> 拿 SESSION cookie + HTML 里的 _csrf token 2. POST /login -> 带上 cookie + _csrf + 用户名密码 3. 用返回的会话 cookie 调 console API CSRF token 通常在登录页的 HTML 里(`` 或 ``)。 Halo 的登录页是 SPA 还是服务端渲染会影响它在不在 —— 所以脚本会 **两种都找**,找不到就明确报出来,不瞎猜。 == 如果还是不行 == 那就得让用户在 Halo 后台建一个**个人令牌**(设置 -> 个人令牌), 拿到之后 `Authorization: Bearer ` 就能用。 那是最可靠的方式 —— 30 秒的事,而且不用把密码给脚本。 ''' from __future__ import annotations import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "_build")) from lssh import run # noqa: E402 PWFILE = ROOT / "_build" / ".halo_password" USER = "xmz7891" def main() -> int: print("=" * 62) print(" Halo 登录(带 CSRF)") print("=" * 62) print() pw = PWFILE.read_text(encoding="utf-8").strip() out = run(r''' rm -f /tmp/hc.txt /tmp/login.html echo "── 1. GET /login(拿 cookie + csrf token)──" curl -s -c /tmp/hc.txt -o /tmp/login.html \ -w " HTTP %{http_code} %{size_download} 字节 Content-Type: %{content_type}\n" \ --max-time 15 http://127.0.0.1:3001/login echo " cookie:" grep -v '^#' /tmp/hc.txt 2>/dev/null | while read -r a b c d e f g; do [ -n "$f" ] && echo " $f" done [ -s /tmp/hc.txt ] || echo " (没拿到 cookie)" echo echo " HTML 里找 csrf token:" grep -oiE '(]*name="_csrf"[^>]*>|name="_csrf"[^>]*value="[^"]*"|"csrf[a-zA-Z]*"\s*:\s*"[^"]*"|csrf-token[^>]*content="[^"]*")' /tmp/login.html 2>/dev/null | head -5 | sed 's/^/ /' echo " HTML 前 400 字:" head -c 400 /tmp/login.html | tr -d '\n' | sed 's/^/ /' echo echo echo " title:" grep -oiE '[^<]*' /tmp/login.html 2>/dev/null | head -2 | sed 's/^/ /' ''', timeout=240, quiet=True) print(out[1] if isinstance(out, tuple) else out) return 0 if __name__ == "__main__": sys.exit(main())