feat(core): mgmt 接入 core 进程管理 + 管理员认证
- gateway 新增 POST /admin/verify 验签接口,供内部服务校验管理员 Token - core 的 /list /exec /kill 接入管理员认证(调用 gateway 验签,fail-closed) - gateway 代理 opened 白名单加入 core:5200 - mgmt 管理页显示当前用户与角色,进程页接入 core 进程管理接口
This commit is contained in:
1 parent
c8812dd27c
commit
34cd295005
6 files changed
+381
-9
No files matched your search
@@ -321,6 +321,11 @@ http::response<http::string_body> handleWhoami(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params);
|
||||
|
||||
/* 内部服务间管理员验签视图:供 core 等内部服务调用,验证 Token 是否为有效管理员 */
|
||||
http::response<http::string_body> handleAdminVerify(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params);
|
||||
|
||||
/* 代理 captcha 图形验证码:pymsg 调 captcha 服务,返回 PNG 图片 */
|
||||
http::response<http::string_body> handleCaptchaImage(
|
||||
const http::request<http::string_body>& req,
|
||||
@@ -871,6 +876,12 @@ void registerRoutes(HttpServer &server) {
|
||||
[](const auto& req, const auto& params) { return handleWhoami(req, params); });
|
||||
});
|
||||
|
||||
/* 内部服务间管理员验签视图(供 core 等调用,须在 post("*") 通配符之前注册) */
|
||||
server.post("/admin/verify", [](const auto& req, const auto& params) -> http::response<http::string_body> {
|
||||
return wrapperHttp(req, params,
|
||||
[](const auto& req, const auto& params) { return handleAdminVerify(req, params); });
|
||||
});
|
||||
|
||||
/* 代理 captcha 图形验证码(须在 get("*") 通配符之前注册) */
|
||||
server.get("/captcha/image", [](const auto& req, const auto& params) -> http::response<http::string_body> {
|
||||
return wrapperHttp(req, params,
|
||||
@@ -1323,6 +1334,132 @@ http::response<http::string_body> handleWhoami(
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 内部服务间管理员验签视图:POST /admin/verify
|
||||
*
|
||||
* 供 core 等内部服务调用,验证请求携带的 Token 是否为有效且未过期的管理员。
|
||||
* Token 来源:Authorization: Bearer 优先,回退 Cookie q4_token。
|
||||
* gateway 是 Token 签发与验证的唯一权威,core 通过本接口完成鉴权,避免密钥共享。
|
||||
*
|
||||
* 响应:{ success, is_admin, user }
|
||||
* - success=true, is_admin=true → 有效管理员(放行)
|
||||
* - success=true, is_admin=false → 非管理员(拒绝)
|
||||
* - success=false → 无 Token / 验签失败 / 过期(拒绝)
|
||||
* 验签失败一律返回 HTTP 401(fail-closed),供内部服务快速判拒绝。 */
|
||||
http::response<http::string_body> handleAdminVerify(
|
||||
const http::request<http::string_body>& req,
|
||||
const std::unordered_map<std::string, std::string>& params)
|
||||
{
|
||||
http::response<http::string_body> res;
|
||||
res.version(req.version());
|
||||
res.set(http::field::content_type, "application/json");
|
||||
res.set("Connection", "close");
|
||||
|
||||
auto _PUBL = MyRSA::Public_Key::from_pem(_KEYGEN->get_public_key_pem());
|
||||
|
||||
/* 获取 Token:Authorization 头优先,回退 Cookie */
|
||||
std::string raw;
|
||||
auto it_auth = req.base().find("Authorization");
|
||||
if (it_auth != req.base().end()) {
|
||||
raw = std::string(it_auth->value());
|
||||
} else {
|
||||
auto cit = req.base().find("Cookie");
|
||||
if (cit != req.base().end()) {
|
||||
std::string cookie_token = get_cookie_value(std::string(cit->value()), "q4_token");
|
||||
if (!cookie_token.empty())
|
||||
raw = "Bearer " + cookie_token;
|
||||
}
|
||||
}
|
||||
|
||||
/* 无 Token 或格式错误:按未授权处理 */
|
||||
if (raw.empty() || raw.substr(0, 7) != "Bearer ") {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
raw = raw.substr(7);
|
||||
size_t idx = raw.find("_");
|
||||
if (idx == std::string::npos) {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string data = raw.substr(0, idx);
|
||||
std::string sign = raw.substr(idx + 1);
|
||||
std::string auth;
|
||||
try { auth = Base64::base64_decode_to_string(data); }
|
||||
catch (const std::exception&) {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* RSA 验签:失败按未授权处理 */
|
||||
if (!_PUBL.Verify(auth, sign)) {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 解析 Token payload */
|
||||
json::value jv_t;
|
||||
try { jv_t = json::parse(auth); }
|
||||
catch (const std::exception&) {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
if (!jv_t.is_object()) {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
json::object& obj = jv_t.as_object();
|
||||
|
||||
/* 过期检查:created_at + keep_time 超过当前时间按无效处理 */
|
||||
auto t = json::value_to<std::string>(obj["created_at"]);
|
||||
auto keep_time_val = json::value_to<int>(obj["keep_time"]);
|
||||
auto created_tp = LocalTime::parse(t);
|
||||
if (created_tp) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto expire_tp = *created_tp + std::chrono::seconds(keep_time_val);
|
||||
if (now > expire_tp) {
|
||||
res.result(http::status::unauthorized);
|
||||
res.body() = R"({"success": false, "is_admin": false})";
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/* 管理员判定:旧 Token 未携带 is_admin 时默认为 false */
|
||||
bool is_admin = false;
|
||||
if (obj.contains("is_admin")) {
|
||||
auto& v = obj["is_admin"];
|
||||
if (v.is_bool()) is_admin = v.as_bool();
|
||||
}
|
||||
|
||||
std::string username;
|
||||
try { username = json::value_to<std::string>(obj["user"]); }
|
||||
catch (...) {}
|
||||
|
||||
json::value jv_r = {
|
||||
{"success", true},
|
||||
{"is_admin", is_admin},
|
||||
{"user", username}
|
||||
};
|
||||
res.result(http::status::ok);
|
||||
res.body() = json::serialize(jv_r);
|
||||
res.prepare_payload();
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 刷新 Token:POST /refresh_token
|
||||
*
|
||||
* 接受当前有效 Token(即使已过期但在宽限期内),签发新 Token。
|
||||
|
||||
Reference in new issue
Block a user