373 lines
10 KiB
C++
373 lines
10 KiB
C++
#include "SessionManager.h"
|
||
|
||
#include <fmt/core.h>
|
||
#include "SafeRNG.h"
|
||
#ifndef _WIN32
|
||
#include "UOHash.h"
|
||
#else
|
||
#include "../UOHash/UOHash.h"
|
||
#endif
|
||
#include <fstream>
|
||
#include <filesystem>
|
||
#include <json/json.h>
|
||
|
||
const std::chrono::hours SessionManager::Session::max_age = std::chrono::hours(24 * 5);
|
||
|
||
SessionManager::Session::Session() //默认构造函数:无效的已过期空Cookie
|
||
{
|
||
uid = -1;
|
||
expiry_date = std::chrono::system_clock::now() - std::chrono::seconds(1);
|
||
}
|
||
|
||
SessionManager::Session::Session(const Session& obj)
|
||
{
|
||
uid = obj.uid;
|
||
cookie = obj.cookie;
|
||
expiry_date = obj.expiry_date;
|
||
}
|
||
|
||
SessionManager::Session::Session(const std::string& cookie)
|
||
{
|
||
uid = -1;
|
||
this->cookie = cookie;
|
||
expiry_date = std::chrono::system_clock::now() - std::chrono::seconds(1);
|
||
}
|
||
|
||
SessionManager::Session::Session(const Json::Value& json_obj)
|
||
{
|
||
if(json_obj["UID"].isInt() && json_obj["Cookie"].isString() && (json_obj["ExpiryDate"].isInt64()))
|
||
{
|
||
uid = json_obj["UID"].asInt();
|
||
cookie = json_obj["Cookie"].asString();
|
||
auto ms = std::chrono::milliseconds(json_obj["ExpiryDate"].asInt64());
|
||
expiry_date = systime(ms);
|
||
}
|
||
else
|
||
{
|
||
uid = -1;
|
||
expiry_date = std::chrono::system_clock::now() - std::chrono::seconds(1);
|
||
}
|
||
}
|
||
|
||
SessionManager::Session::Session(int uid, const std::string& cookie)
|
||
{
|
||
this->uid = uid;
|
||
this->cookie = cookie;
|
||
expiry_date = std::chrono::system_clock::now() + max_age;
|
||
}
|
||
|
||
int SessionManager::Session::GetUID() const
|
||
{
|
||
return uid;
|
||
}
|
||
|
||
bool SessionManager::Session::Expired() const
|
||
{
|
||
return (expiry_date < std::chrono::system_clock::now());
|
||
}
|
||
|
||
std::string SessionManager::Session::GetCookie() const
|
||
{
|
||
return cookie;
|
||
}
|
||
|
||
SessionManager::systime SessionManager::Session::GetExpiryDate() const
|
||
{
|
||
return expiry_date;
|
||
}
|
||
|
||
bool SessionManager::Session::NeedExpiryDateRefresh() const
|
||
{
|
||
if (Expired())
|
||
return true;
|
||
auto usage_time = std::chrono::system_clock::now() - (expiry_date - max_age);
|
||
return (usage_time >= std::chrono::hours(24));
|
||
}
|
||
|
||
void SessionManager::Session::SetUID(int uid)
|
||
{
|
||
this->uid = uid;
|
||
}
|
||
|
||
void SessionManager::Session::RefreshExpiryDate()
|
||
{
|
||
expiry_date = std::chrono::system_clock::now() + max_age;
|
||
}
|
||
|
||
void SessionManager::Session::SetCookie(const std::string& cookie)
|
||
{
|
||
this->cookie = cookie;
|
||
}
|
||
|
||
void SessionManager::Session::SetExpiryDate(const systime& expiry_time)
|
||
{
|
||
this->expiry_date = expiry_date;
|
||
}
|
||
|
||
bool SessionManager::Session::operator<(const Session& obj) const
|
||
{
|
||
return (this->cookie < obj.cookie);
|
||
}
|
||
|
||
SessionManager::Session::operator Json::Value() const
|
||
{
|
||
Json::Value root;
|
||
root["UID"] = uid;
|
||
root["Cookie"] = cookie;
|
||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(expiry_date.time_since_epoch()).count();
|
||
root["ExpiryDate"] = Json::Int64(ms);
|
||
return root;
|
||
}
|
||
|
||
int SessionManager::Session::GetMaxAgeSeconds()
|
||
{
|
||
return (max_age.count() * 3600);
|
||
}
|
||
|
||
SessionManager::SessionManager()
|
||
{
|
||
}
|
||
|
||
std::string SessionManager::Random()
|
||
{
|
||
return RandomNumberGenerator::SecureRandomHex(32);
|
||
}
|
||
|
||
std::string SessionManager::ISO8601_TimeString()
|
||
{
|
||
time_t now = time(nullptr);
|
||
tm tim = {};
|
||
#if defined(_WIN32)
|
||
localtime_s(&tim, &now); // Windows
|
||
#else
|
||
localtime_r(&now, &tim); // POSIX
|
||
#endif
|
||
return fmt::format("{}-{}-{}T{}:{}:{}+08:00", (tim.tm_year + 1900), (tim.tm_mon + 1), tim.tm_mday, tim.tm_hour, tim.tm_min, tim.tm_sec);
|
||
}
|
||
|
||
void SessionManager::AutoCleanCookiePool()
|
||
{
|
||
std::lock_guard<std::mutex> lock(pool_mutex); //自动清理前需要上锁
|
||
for (auto it = cookie_storage.begin(); it != cookie_storage.end(); ) //正向遍历,删除无效/过期Session
|
||
{
|
||
const std::string& cookie = it->first;
|
||
int uid = it->second;
|
||
|
||
auto rit = reverse_cookie_storage.find(uid);
|
||
if (rit == reverse_cookie_storage.end()) //有Cookie无Session,删掉
|
||
{
|
||
it = cookie_storage.erase(it);
|
||
continue;
|
||
}
|
||
|
||
Session index_session(cookie.substr(prefix.length()));
|
||
auto& rcs = rit->second;
|
||
auto sit = rcs.find(index_session);
|
||
if ((sit == rcs.end()) || sit->Expired()) //无Session或Session已过期,删掉
|
||
{
|
||
if (sit != rcs.end())
|
||
rcs.erase(sit);
|
||
if (rcs.empty())
|
||
reverse_cookie_storage.erase(rit);
|
||
it = cookie_storage.erase(it);
|
||
continue;
|
||
}
|
||
++it; //前置++性能开销较低
|
||
}
|
||
|
||
for (auto rit = reverse_cookie_storage.begin(); rit != reverse_cookie_storage.end(); ) //反向遍历,删除异常Session
|
||
{
|
||
auto& rcs = rit->second;
|
||
for (auto sit = rcs.begin(); sit != rcs.end(); )
|
||
{
|
||
std::string key = prefix + sit->GetCookie();
|
||
if (cookie_storage.find(key) == cookie_storage.end()) //正向没有反向有,异常Session(无法被利用)
|
||
{
|
||
auto sit_next = std::next(sit);
|
||
rcs.erase(sit);
|
||
sit = sit_next;
|
||
}
|
||
else
|
||
++sit;
|
||
}
|
||
if (rcs.empty())
|
||
{
|
||
auto rit_next = std::next(rit);
|
||
reverse_cookie_storage.erase(rit);
|
||
rit = rit_next;
|
||
}
|
||
else
|
||
++rit;
|
||
}
|
||
}
|
||
|
||
std::string SessionManager::GenerateCookieForUser(int uid, bool cookie_only)
|
||
{
|
||
//生成Cookie和Session
|
||
std::string str_uid = std::to_string(uid);
|
||
std::string raw_cookie = str_uid + ISO8601_TimeString() + str_uid + Random() + str_uid;
|
||
auto res = uns::UOHash::HashString(uns::HashID::SHA3_224, raw_cookie);
|
||
if (!res)
|
||
return "";
|
||
std::string cookie = res.GetResult();
|
||
Session session(uid, cookie);
|
||
//存储到Cookie池 - 使用互斥体保证线程安全
|
||
{
|
||
std::lock_guard<std::mutex> lock(pool_mutex);
|
||
cookie_storage.insert({ (prefix + cookie), uid });
|
||
if (reverse_cookie_storage.find(uid) == reverse_cookie_storage.end())
|
||
reverse_cookie_storage.insert({ uid, { session } });
|
||
else
|
||
reverse_cookie_storage.at(uid).insert(session);
|
||
}
|
||
//返回用于响应的Cookie串
|
||
if(cookie_only)
|
||
return (prefix + cookie);
|
||
else
|
||
return fmt::format(fmt::runtime(cookie_template), cookie, Session::GetMaxAgeSeconds());
|
||
}
|
||
|
||
std::string SessionManager::DeleteCookie(const std::string& cookie)
|
||
{
|
||
std::lock_guard<std::mutex> lock(pool_mutex);
|
||
if (cookie_storage.find(cookie) != cookie_storage.end())
|
||
{
|
||
int uid = cookie_storage.at(cookie);
|
||
cookie_storage.erase(cookie);
|
||
if (reverse_cookie_storage.find(uid) != reverse_cookie_storage.end())
|
||
{
|
||
auto& rcs = reverse_cookie_storage.at(uid);
|
||
Session index_session(cookie.substr(prefix.length()));
|
||
rcs.erase(index_session);
|
||
if (rcs.empty())
|
||
reverse_cookie_storage.erase(uid);
|
||
}
|
||
}
|
||
//返回用于响应的Cookie串(注销浏览器端的Cookie)
|
||
return fmt::format(fmt::runtime(cookie_template.substr(prefix.length())), cookie, 0);
|
||
}
|
||
|
||
bool SessionManager::CheckRequestCookie(uns::RequestPtr request, int& uid)
|
||
{
|
||
uid = -1;
|
||
if (!request->HasHeader("Cookie")) //未找到Cookie
|
||
return false;
|
||
std::string cookie = request->GetHeader("Cookie");
|
||
std::lock_guard<std::mutex> lock(pool_mutex); //使用互斥体保证Cookie池的线程安全
|
||
auto cit = cookie_storage.find(cookie);
|
||
if (cit == cookie_storage.end()) //不正确的Cookie
|
||
return false;
|
||
uid = cit->second;
|
||
if (reverse_cookie_storage.find(uid) == reverse_cookie_storage.end())
|
||
return false; //理论上不会出现有Cookie没有UID的情况,仅作兜底处理
|
||
Session index_session(cookie.substr(prefix.length()));
|
||
auto& rcs = reverse_cookie_storage.at(uid);
|
||
auto it = rcs.find(index_session);
|
||
if (it == rcs.end()) //无有效Session
|
||
return false;
|
||
if (it->Expired()) //Cookie过期,删除该Session及对应的cookie。
|
||
{
|
||
rcs.erase(it); //删除Session
|
||
if (rcs.empty()) //如果一个用户没有任何有效的Cookie,删除该用户的记录
|
||
reverse_cookie_storage.erase(uid);
|
||
cookie_storage.erase(cookie); //删除Cookie
|
||
return false;
|
||
}
|
||
if (it->NeedExpiryDateRefresh()) //根据需要决定是否刷新Cookie
|
||
{
|
||
auto session = rcs.extract(it);
|
||
session.value().RefreshExpiryDate();
|
||
rcs.insert(std::move(session));
|
||
}
|
||
return true; //到达此处意味着找到有效的Cookie,并已完成必要的更新工作
|
||
}
|
||
|
||
std::string SessionManager::DeleteAllCookieForUser(int uid, const std::string & current_cookie)
|
||
{
|
||
std::lock_guard<std::mutex> lock(pool_mutex);
|
||
if (reverse_cookie_storage.find(uid) != reverse_cookie_storage.end())
|
||
{
|
||
auto sessions = reverse_cookie_storage.at(uid);
|
||
reverse_cookie_storage.erase(uid);
|
||
for(const auto& session : sessions)
|
||
{
|
||
if(cookie_storage.find(session.GetCookie()) != cookie_storage.end())
|
||
cookie_storage.erase(session.GetCookie());
|
||
}
|
||
}
|
||
//返回用于响应的Cookie串(注销浏览器端的Cookie)
|
||
return fmt::format(fmt::runtime(cookie_template.substr(prefix.length())), current_cookie, 0);
|
||
}
|
||
|
||
bool SessionManager::HasDumpedCookie(const std::string& path)
|
||
{
|
||
namespace fs = std::filesystem;
|
||
fs::path fn = (fs::path(path) / "unsc_sessions.json");
|
||
std::error_code ec;
|
||
return fs::is_regular_file(fn, ec);
|
||
}
|
||
|
||
bool SessionManager::LoadDumpedCookie(const std::string& path)
|
||
{
|
||
namespace fs = std::filesystem;
|
||
fs::path fn = (fs::path(path) / "unsc_sessions.json");
|
||
std::fstream fin(fn.string(), std::ios::in);
|
||
if(!fin.is_open())
|
||
return false;
|
||
try
|
||
{
|
||
Json::Reader reader;
|
||
Json::Value cookies;
|
||
if(!reader.parse(fin, cookies, false))
|
||
{
|
||
fin.close();
|
||
return false;
|
||
}
|
||
fin.close();
|
||
if(!cookies.isArray())
|
||
return false;
|
||
std::lock_guard<std::mutex> lock(pool_mutex); //使用互斥体保证Cookie池的线程安全
|
||
for(const auto& cookie : cookies)
|
||
{
|
||
Session session(cookie);
|
||
if(session.Expired())
|
||
continue;
|
||
int uid = session.GetUID();
|
||
cookie_storage.insert({ (prefix + session.GetCookie()), uid });
|
||
if (reverse_cookie_storage.find(uid) == reverse_cookie_storage.end())
|
||
reverse_cookie_storage.insert({ uid, { session } });
|
||
else
|
||
reverse_cookie_storage.at(uid).insert(session);
|
||
}
|
||
std::error_code ec;
|
||
fs::remove(fn, ec);
|
||
return true;
|
||
}
|
||
catch(...)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool SessionManager::DumpAllValidCookies(const std::string& path)
|
||
{
|
||
namespace fs = std::filesystem;
|
||
fs::path fn = (fs::path(path) / "unsc_sessions.json");
|
||
std::fstream fout(fn.string(), std::ios::out | std::ios::trunc);
|
||
if(!fout.is_open())
|
||
return false;
|
||
Json::Value cookies(Json::arrayValue);
|
||
{
|
||
std::lock_guard<std::mutex> lock(pool_mutex); //使用互斥体保证Cookie池的线程安全
|
||
for(const auto& [uid, sessions] : reverse_cookie_storage)
|
||
for(const auto& session : sessions)
|
||
cookies.append(session);
|
||
}
|
||
Json::FastWriter writer;
|
||
fout << writer.write(cookies) << std::endl;
|
||
fout.close();
|
||
return true;
|
||
}
|
||
|
||
SessionManager GlobalSessionManager;
|