From d1b8bf63420fb88edb8e43a1a4d70ddf078ca262 Mon Sep 17 00:00:00 2001
From: root <root@Ubuntu.amass.fun>
Date: Sat, 1 Mar 2025 09:09:30 +0000
Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=99=BB=E5=BD=95=E6=8E=A5?=
 =?UTF-8?q?=E5=8F=A3=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Application.cpp         |   3 +
 Application.h           |   2 +
 Base/CMakeLists.txt     |  12 +++
 Base/DataStructure.cpp  |  71 +++++++++++++++
 Base/DataStructure.h    |  31 +++++--
 CMakeLists.txt          |   5 ++
 Database.cpp            | 131 +++++++++++++++++++++++++++-
 Database.h              |   4 +
 ServiceLogic.cpp        | 188 ++++++++++++++++++++++++++++++++++++++++
 ServiceLogic.h          |   1 +
 SessionStore.cpp        | 106 ++++++++++++++++++++++
 SessionStore.h          |  39 +++++++++
 UnitTest/Account.cpp    |  10 +++
 UnitTest/CMakeLists.txt |  11 +++
 UnitTest/main.cpp       |   2 +
 15 files changed, 608 insertions(+), 8 deletions(-)
 create mode 100644 Base/CMakeLists.txt
 create mode 100644 Base/DataStructure.cpp
 create mode 100644 SessionStore.cpp
 create mode 100644 SessionStore.h
 create mode 100644 UnitTest/Account.cpp
 create mode 100644 UnitTest/CMakeLists.txt
 create mode 100644 UnitTest/main.cpp

diff --git a/Application.cpp b/Application.cpp
index 237d9e5..7f2669f 100644
--- a/Application.cpp
+++ b/Application.cpp
@@ -7,6 +7,7 @@
 #include "HttpSession.h"
 #include "Router/router.hpp"
 #include "ServiceLogic.h"
+#include "SessionStore.h"
 #include "Settings.h"
 #include "WeChat/Corporation/Context.h"
 #include <boost/asio/strand.hpp>
@@ -31,6 +32,7 @@ Application::Application() : m_d{new ApplicationPrivate()} {
 
     m_database = Singleton<Database>::construct();
     m_database->open(m_settings->sqlitePath());
+    m_sessionStore = Singleton<SessionStore>::construct(*m_ioContext->ioContext());
 
     m_corporationContext = Singleton<WeChat::Corporation::Context>::construct(*m_ioContext->ioContext());
     m_corporationContext->start();
@@ -84,6 +86,7 @@ int Application::exec() {
     auto settings = Singleton<Settings>::instance();
     ServiceLogic::live2dBackend();
     ServiceLogic::visitAnalysis();
+    ServiceLogic::userAccount();
     startAcceptHttpConnections(settings->server(), settings->port());
     m_ioContext->run();
     return 0;
diff --git a/Application.h b/Application.h
index 4a85161..5a4f743 100644
--- a/Application.h
+++ b/Application.h
@@ -23,6 +23,7 @@ class Settings;
 class ApplicationPrivate;
 class HttpSession;
 class Database;
+class SessionStore;
 
 class Application : public std::enable_shared_from_this<Application> {
 public:
@@ -43,6 +44,7 @@ private:
     std::shared_ptr<Settings> m_settings;
     std::shared_ptr<Core::IoContext> m_ioContext;
     std::shared_ptr<Core::MessageManager> m_messageManager;
+    std::shared_ptr<SessionStore> m_sessionStore;
     std::shared_ptr<Database> m_database;
     std::shared_ptr<WeChat::Corporation::Context> m_corporationContext;
 };
diff --git a/Base/CMakeLists.txt b/Base/CMakeLists.txt
new file mode 100644
index 0000000..15ac834
--- /dev/null
+++ b/Base/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_library(Base
+    DataStructure.h DataStructure.cpp
+)
+
+get_filename_component(PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
+target_include_directories(Base
+    INTERFACE  ${PARENT_DIR}
+)
+
+target_link_libraries(Base
+    PRIVATE OpenSSL::Crypto
+)
\ No newline at end of file
diff --git a/Base/DataStructure.cpp b/Base/DataStructure.cpp
new file mode 100644
index 0000000..8ed1bef
--- /dev/null
+++ b/Base/DataStructure.cpp
@@ -0,0 +1,71 @@
+#include "DataStructure.h"
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include <openssl/rand.h>
+#include <regex>
+#include <stdexcept>
+
+namespace Older {
+int PBKDF2_ITERATIONS = 100000;
+constexpr int SALT_LENGTH = 16;
+constexpr int HASH_LENGTH = 32;
+
+Account Account::hashPassword(const std::string &password) {
+    Account ret;
+
+    // 生成随机盐
+    ret.salt.resize(SALT_LENGTH);
+    if (RAND_bytes(ret.salt.data(), SALT_LENGTH) != 1) {
+        throw std::runtime_error("Salt generation failed");
+    }
+
+    // PBKDF2 派生
+    EVP_KDF *kdf = EVP_KDF_fetch(nullptr, "PBKDF2", nullptr);
+    EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
+
+    OSSL_PARAM params[] = {OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0),
+                           OSSL_PARAM_construct_octet_string("salt", ret.salt.data(), ret.salt.size()),
+                           OSSL_PARAM_construct_octet_string("pass", (void *)password.data(), password.size()),
+                           OSSL_PARAM_construct_int("iter", &PBKDF2_ITERATIONS), OSSL_PARAM_construct_end()};
+
+    ret.passwordHash.resize(HASH_LENGTH);
+    if (EVP_KDF_derive(ctx, ret.passwordHash.data(), HASH_LENGTH, params) != 1) {
+        EVP_KDF_CTX_free(ctx);
+        EVP_KDF_free(kdf);
+        throw std::runtime_error("PBKDF2 failed");
+    }
+
+    EVP_KDF_CTX_free(ctx);
+    EVP_KDF_free(kdf);
+    return ret;
+}
+
+bool Account::verifyPassword(const Account &account, const std::string &password) {
+    // 重新计算哈希
+    EVP_KDF *kdf = EVP_KDF_fetch(nullptr, "PBKDF2", nullptr);
+    EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
+
+    OSSL_PARAM params[5] = {OSSL_PARAM_construct_utf8_string("digest", const_cast<char *>("SHA256"), 0),
+                            OSSL_PARAM_construct_octet_string("salt", (void *)account.salt.data(), account.salt.size()),
+                            OSSL_PARAM_construct_octet_string("pass", const_cast<char *>(password.data()), password.size()),
+                            OSSL_PARAM_construct_int("iter", &PBKDF2_ITERATIONS), OSSL_PARAM_construct_end()};
+
+    std::vector<unsigned char> new_hash(HASH_LENGTH);
+    if (EVP_KDF_derive(ctx, new_hash.data(), HASH_LENGTH, params) != 1) {
+        EVP_KDF_CTX_free(ctx);
+        EVP_KDF_free(kdf);
+        return false;
+    }
+
+    EVP_KDF_CTX_free(ctx);
+    EVP_KDF_free(kdf);
+
+    // 安全比较(防时序攻击)
+    return CRYPTO_memcmp(account.passwordHash.data(), new_hash.data(), HASH_LENGTH) == 0;
+}
+
+bool Account::validateEmail(const std::string &email) {
+    const std::regex pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
+    return std::regex_match(email, pattern);
+}
+} // namespace Older
\ No newline at end of file
diff --git a/Base/DataStructure.h b/Base/DataStructure.h
index 7530eea..a039483 100644
--- a/Base/DataStructure.h
+++ b/Base/DataStructure.h
@@ -1,17 +1,34 @@
 #ifndef __DATASTRUCTURE_H__
 #define __DATASTRUCTURE_H__
 
+#include <cstdint>
 #include <string>
+#include <vector>
 
-struct VisitorStats{
+namespace Older {
+struct VisitorStats {
     std::string url;
-    int visitorCount=0;
-    int totalViews =0;
-    int64_t lastViewTime =0;
+    int visitorCount = 0;
+    int totalViews = 0;
+    int64_t lastViewTime = 0;
 };
 
-struct SiteStats{
-    int totalViews =0;
-    int totalVisitors =0;
+struct SiteStats {
+    int totalViews = 0;
+    int totalVisitors = 0;
 };
+
+struct Account {
+    int64_t id = 0;
+    std::string username;
+    std::string email;
+    std::vector<uint8_t> passwordHash;
+    std::vector<uint8_t> salt;
+    int64_t createdAt = 0;
+
+    static Account hashPassword(const std::string &password);
+    static bool verifyPassword(const Account &account, const std::string &password);
+    static bool validateEmail(const std::string& email);
+};}
+
 #endif // __DATASTRUCTURE_H__
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a9909e8..6d82777 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,10 @@
 find_package(Boost REQUIRED COMPONENTS json)
+find_package(OpenSSL REQUIRED)
 
 add_subdirectory(/root/Projects/Kylin Kylin)
 add_subdirectory(3rdparty)
+add_subdirectory(Base)
+add_subdirectory(UnitTest)
 
 add_executable(Older main.cpp
     WeChat/Corporation/Context.h WeChat/Corporation/Context.cpp
@@ -11,6 +14,7 @@ add_executable(Older main.cpp
     HttpSession.h HttpSession.cpp
     ResponseUtility.h ResponseUtility.cpp
     ServiceLogic.h ServiceLogic.inl ServiceLogic.cpp
+    SessionStore.h SessionStore.cpp
     Settings.h Settings.cpp
 )
 
@@ -19,6 +23,7 @@ target_include_directories(Older
 )
 
 target_link_libraries(Older
+    PRIVATE Base
     PRIVATE Kylin::Core
     PRIVATE Kylin::Http
     PRIVATE Kylin::Router
diff --git a/Database.cpp b/Database.cpp
index 7ea7853..63f6e03 100644
--- a/Database.cpp
+++ b/Database.cpp
@@ -114,8 +114,118 @@ SiteStats Database::siteStats() {
     return ret;
 }
 
+void Database::createUser(const Account &account) {
+    if (!Account::validateEmail(account.email)) {
+        throw std::runtime_error("Invalid email format");
+    }
+
+    sqlite3_stmt *statement = nullptr;
+    const char *sql = "INSERT INTO users (username, email, password_hash, salt, created_at) VALUES (?, ?, ?, ?, ?);";
+
+    if (sqlite3_prepare_v2(m_sqlite, sql, -1, &statement, nullptr) != SQLITE_OK) {
+        throw std::runtime_error(sqlite3_errmsg(m_sqlite));
+    }
+
+    sqlite3_bind_text(statement, 1, account.username.c_str(), -1, SQLITE_STATIC);
+    sqlite3_bind_text(statement, 2, account.email.c_str(), -1, SQLITE_STATIC); // 新增绑定
+    sqlite3_bind_blob(statement, 3, account.passwordHash.data(), account.passwordHash.size(), SQLITE_STATIC);
+    sqlite3_bind_blob(statement, 4, account.salt.data(), account.salt.size(), SQLITE_STATIC);
+    sqlite3_bind_int64(statement, 5, account.createdAt);
+
+    int rc = sqlite3_step(statement);
+    sqlite3_finalize(statement);
+
+    if (rc != SQLITE_DONE) {
+        const char *message = sqlite3_errmsg(m_sqlite);
+        if (std::string(message).find("UNIQUE") != std::string::npos) {
+            throw std::runtime_error("username or email already exists");
+        }
+        throw std::runtime_error(message);
+    }
+}
+
+Account Database::user(const std::string &identifier) const {
+    sqlite3_stmt *stmt;
+    const char *sql = "SELECT id, username, email, password_hash, salt, created_at "
+                      "FROM users WHERE username = ? OR email = ?;";
+
+    if (sqlite3_prepare_v2(m_sqlite, sql, -1, &stmt, nullptr) != SQLITE_OK) {
+        throw std::runtime_error(sqlite3_errmsg(m_sqlite));
+    }
+
+    sqlite3_bind_text(stmt, 1, identifier.c_str(), -1, SQLITE_STATIC);
+    sqlite3_bind_text(stmt, 2, identifier.c_str(), -1, SQLITE_STATIC);
+
+    if (sqlite3_step(stmt) != SQLITE_ROW) {
+        sqlite3_finalize(stmt);
+        throw std::runtime_error("User not found");
+    }
+
+    Account ret;
+
+    ret.id = sqlite3_column_int(stmt, 0);
+
+    ret.username = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
+    ret.email = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 2));
+
+    const void *hash_blob = sqlite3_column_blob(stmt, 3);
+    int hash_size = sqlite3_column_bytes(stmt, 3);
+    ret.passwordHash.resize(hash_size);
+    memcpy(ret.passwordHash.data(), hash_blob, hash_size);
+
+    const void *salt_blob = sqlite3_column_blob(stmt, 4);
+    int salt_size = sqlite3_column_bytes(stmt, 4);
+    ret.salt.resize(salt_size);
+    memcpy(ret.salt.data(), salt_blob, salt_size);
+
+    ret.createdAt = sqlite3_column_int64(stmt, 5);
+
+    sqlite3_finalize(stmt);
+    return ret;
+}
+
+Account Database::user(int64_t id) const {
+    sqlite3_stmt *stmt;
+    const char *sql = "SELECT id, username, email, password_hash, salt, created_at "
+                      "FROM users WHERE id = ?;";
+
+    if (sqlite3_prepare_v2(m_sqlite, sql, -1, &stmt, nullptr) != SQLITE_OK) {
+        throw std::runtime_error(sqlite3_errmsg(m_sqlite));
+    }
+
+    sqlite3_bind_int64(stmt, 1, id);
+
+    if (sqlite3_step(stmt) != SQLITE_ROW) {
+        sqlite3_finalize(stmt);
+        throw std::runtime_error("User not found");
+    }
+
+    Account ret;
+
+    ret.id = sqlite3_column_int(stmt, 0);
+
+    ret.username = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 1));
+    ret.email = reinterpret_cast<const char *>(sqlite3_column_text(stmt, 2));
+
+    const void *hash_blob = sqlite3_column_blob(stmt, 3);
+    int hash_size = sqlite3_column_bytes(stmt, 3);
+    ret.passwordHash.resize(hash_size);
+    memcpy(ret.passwordHash.data(), hash_blob, hash_size);
+
+    const void *salt_blob = sqlite3_column_blob(stmt, 4);
+    int salt_size = sqlite3_column_bytes(stmt, 4);
+    ret.salt.resize(salt_size);
+    memcpy(ret.salt.data(), salt_blob, salt_size);
+
+    ret.createdAt = sqlite3_column_int64(stmt, 5);
+
+    sqlite3_finalize(stmt);
+    return ret;
+}
+
 void Database::initialize() {
     createVisitAnalysisTable();
+    createUsersTable();
 }
 
 void Database::createVisitAnalysisTable() {
@@ -138,4 +248,23 @@ void Database::createVisitAnalysisTable() {
         sqlite3_free(message);
     }
 }
-}
\ No newline at end of file
+
+void Database::createUsersTable() {
+    const char *sql = R"(
+        CREATE TABLE IF NOT EXISTS users (
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
+            username TEXT UNIQUE NOT NULL,
+            email TEXT UNIQUE NOT NULL,
+            password_hash BLOB NOT NULL,
+            salt BLOB NOT NULL,
+            created_at INTEGER NOT NULL
+        );
+    )";
+    char *message = nullptr;
+    int rc = sqlite3_exec(m_sqlite, sql, nullptr, nullptr, &message);
+    if (rc != SQLITE_OK) {
+        LOG(error) << "Error creating table: " << message;
+        sqlite3_free(message);
+    }
+}
+} // namespace Older
\ No newline at end of file
diff --git a/Database.h b/Database.h
index 2b266c0..34700c3 100644
--- a/Database.h
+++ b/Database.h
@@ -18,9 +18,13 @@ public:
     std::list<VisitorStats> mostViewedUrls(int n);
     std::list<VisitorStats> latestViewedUrls(int n);
     SiteStats siteStats();
+    void createUser(const Account &account);
+    Account user(const std::string &identifier)const;
+    Account user(int64_t id)const;
 
 protected:
     void createVisitAnalysisTable();
+    void createUsersTable();
     void initialize();
 
 private:
diff --git a/ServiceLogic.cpp b/ServiceLogic.cpp
index 502983b..0b8a173 100644
--- a/ServiceLogic.cpp
+++ b/ServiceLogic.cpp
@@ -2,12 +2,32 @@
 #include "Core/Singleton.h"
 #include "Database.h"
 #include "HttpSession.h"
+#include "SessionStore.h"
 #include "Settings.h"
 #include <sstream>
 
 namespace ServiceLogic {
 using namespace boost::beast;
 
+std::string extractToken(const std::string &cookieHeader, const std::string &tokenName = "access_token") {
+    // 格式示例:"access_token=abc123; Path=/; Expires=Wed, 21 Oct 2023 07:28:00 GMT"
+    size_t startPos = cookieHeader.find(tokenName + "=");
+    if (startPos == std::string::npos) {
+        return "";
+    }
+    startPos += tokenName.size() + 1; // 跳过 "token_name="
+    size_t endPos = cookieHeader.find(';', startPos);
+    if (endPos == std::string::npos) {
+        endPos = cookieHeader.size();
+    }
+    std::string token = cookieHeader.substr(startPos, endPos - startPos);
+
+    // 移除可能的引号和空格
+    token.erase(std::remove(token.begin(), token.end(), '"'), token.end());
+    token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
+    return token;
+}
+
 boost::beast::http::response<boost::beast::http::string_body>
 serverError(const boost::beast::http::request<boost::beast::http::string_body> &request, std::string_view errorMessage) {
     using namespace boost::beast;
@@ -206,4 +226,172 @@ void live2dBackend() {
     });
 }
 
+void userAccount() {
+    using namespace Core;
+    auto application = Singleton<Older::Application>::instance();
+    application->insertUrl("/api/v1/user/register", [](Older::HttpSession &session, const Older::Application::Request &request,
+                                                       const boost::urls::matches &matches) {
+        auto rootJson = boost::json::parse(request.body());
+        auto &root = rootJson.as_object();
+        boost::json::object reply;
+        if (root.contains("username") && root.contains("password") && root.contains("email")) {
+            try {
+                auto account = Older::Account::hashPassword(std::string(root.at("password").as_string()));
+                account.username = root.at("username").as_string();
+                account.email = root.at("email").as_string();
+                auto database = Singleton<Older::Database>::instance();
+                database->createUser(account);
+
+                reply["code"] = 200;
+                reply["message"] = "register success";
+                account = database->user(account.username);
+
+                boost::json::object data;
+                data["user_id"] = account.id;
+                reply["data"] = std::move(data);
+            } catch (const std::exception &e) {
+                reply["code"] = 400;
+                reply["message"] = e.what();
+            }
+
+        } else {
+            reply["code"] = 400;
+            reply["message"] = "missing username, password or email";
+        }
+        http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
+        s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
+        s.set(http::field::content_type, "application/json;charset=UTF-8");
+        s.keep_alive(request.keep_alive());
+        s.body() = boost::json::serialize(reply);
+        s.prepare_payload();
+        session.reply(std::move(s));
+    });
+
+    application->insertUrl("/api/v1/user/login", [](Older::HttpSession &session, const Older::Application::Request &request,
+                                                    const boost::urls::matches &matches) {
+        auto rootJson = boost::json::parse(request.body());
+        auto &root = rootJson.as_object();
+        boost::json::object reply;
+        std::string cookie;
+        if (root.contains("username") && root.contains("password")) {
+            try {
+                auto database = Singleton<Older::Database>::instance();
+                auto sessions = Singleton<Older::SessionStore>::instance();
+                auto account = database->user(std::string(root.at("username").as_string()));
+                bool logined = Older::Account::verifyPassword(account, std::string(root.at("password").as_string()));
+
+                reply["code"] = logined ? 200 : 404;
+                reply["message"] = logined ? "login success" : "wrong password or user";
+                account = database->user(account.username);
+                if (logined) {
+                    boost::json::object data;
+                    data["user_id"] = account.id;
+                    reply["data"] = std::move(data);
+                    // clang-format off
+                    cookie = "older_auth=" + sessions->addSession(account.id)
+                    + "; Path=/"
+                    + "; HttpOnly"          // 阻止JS访问
+                    + "; Secure"            // 仅HTTPS传输
+                    + "; SameSite=Strict"   // 防止CSRF
+                    + "; Max-Age=86400";   // 24小时过期
+                    // clang-format on
+                }
+            } catch (const std::exception &e) {
+                reply["code"] = 400;
+                reply["message"] = e.what();
+            }
+        } else {
+            reply["code"] = 400;
+            reply["message"] = "missing username, password or email";
+        }
+        http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
+        s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
+        s.set(http::field::content_type, "application/json;charset=UTF-8");
+        if (!cookie.empty()) {
+            s.set(http::field::set_cookie, cookie);
+        }
+        s.keep_alive(request.keep_alive());
+        s.body() = boost::json::serialize(reply);
+        s.prepare_payload();
+        session.reply(std::move(s));
+    });
+
+    application->insertUrl("/api/v1/user/verify", [](Older::HttpSession &session, const Older::Application::Request &request,
+                                                     const boost::urls::matches &matches) {
+        auto cookies = request.find(http::field::cookie);
+        boost::json::object reply;
+        std::string cookie;
+        if (cookies == request.end()) {
+            reply["code"] = 401;
+            reply["message"] = "cookie is not exists";
+        } else {
+            auto sessions = Singleton<Older::SessionStore>::instance();
+            auto database = Singleton<Older::Database>::instance();
+            std::string accessToken = extractToken(cookies->value(), "older_auth");
+            auto [valid, newAccessToken] = sessions->validateAndRefresh(accessToken);
+
+            if (valid) {
+                if (newAccessToken != accessToken) { // 需要刷新令牌
+                    // clang-format off
+                    cookie = "older_auth=" + newAccessToken
+                    + "; Path=/"
+                    + "; HttpOnly"          // 阻止JS访问
+                    + "; Secure"            // 仅HTTPS传输
+                    + "; SameSite=Strict"   // 防止CSRF
+                    + "; Max-Age=86400";   // 24小时过期
+                    // clang-format on
+                }
+                reply["code"] = 200;
+                reply["message"] = "verify success";
+                boost::json::object data;
+                auto d = sessions->at(newAccessToken);
+                auto account = database->user(d.userId);
+                data["user_id"] = d.userId;
+                data["username"] = account.username;
+                data["email"] = account.email;
+                reply["data"] = std::move(data);
+            } else {
+                reply["code"] = 401;
+                reply["message"] = "cookie is invalid";
+            }
+        }
+
+        http::response<boost::beast::http::string_body> s{boost::beast::http::status::ok, request.version()};
+        s.set(http::field::server, BOOST_BEAST_VERSION_STRING);
+        s.set(http::field::content_type, "application/json;charset=UTF-8");
+        if (!cookie.empty()) {
+            s.set(http::field::set_cookie, cookie);
+        }
+        s.keep_alive(request.keep_alive());
+        s.body() = boost::json::serialize(reply);
+        s.prepare_payload();
+        session.reply(std::move(s));
+    });
+
+    application->insertUrl("/api/v1/user/logout", [](Older::HttpSession &session, const Older::Application::Request &request,
+                                                     const boost::urls::matches &matches) {
+        http::response<boost::beast::http::string_body> res{boost::beast::http::status::ok, request.version()};
+        try {
+            auto sessions = Singleton<Older::SessionStore>::instance();
+            auto cookies = request.find(http::field::cookie);
+            if (cookies != request.end()) {
+                std::string accessToken = extractToken(cookies->value(), "older_auth");
+                if (!accessToken.empty()) {
+                    sessions->removeSession(accessToken);
+                }
+            }
+
+            res.set(http::field::content_type, "application/json");
+            res.set(http::field::set_cookie, "older_auth=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure");
+            res.set(http::field::set_cookie,
+                    "older_refresh_token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Secure");
+            res.body() = R"({"code": 401, "message": "logout successfully"})";
+        } catch (const std::exception &e) {
+            res.result(http::status::internal_server_error);
+            res.body() = R"({"status": "error", "message": ")" + std::string(e.what()) + "\"}";
+        }
+        session.reply(std::move(res));
+    });
+}
+
 } // namespace ServiceLogic
diff --git a/ServiceLogic.h b/ServiceLogic.h
index 6fa70ab..8f2bbe2 100644
--- a/ServiceLogic.h
+++ b/ServiceLogic.h
@@ -43,6 +43,7 @@ boost::beast::http::response<ResponseBody> make_200(const boost::beast::http::re
 
 void live2dBackend();
 void visitAnalysis();
+void userAccount();
 
 
 
diff --git a/SessionStore.cpp b/SessionStore.cpp
new file mode 100644
index 0000000..7fafe20
--- /dev/null
+++ b/SessionStore.cpp
@@ -0,0 +1,106 @@
+#include "SessionStore.h"
+#include <boost/uuid/uuid_generators.hpp>
+#include <boost/uuid/uuid_io.hpp>
+namespace Older {
+SessionStore::SessionStore(boost::asio::io_context &ioContext) : m_ioContext(ioContext), m_cleanupTimer(ioContext) {
+    startCleanupTask();
+}
+
+std::string SessionStore::addSession(int userId, std::chrono::minutes sessionLifetime, std::chrono::minutes refreshInterval) {
+    using namespace boost::uuids;
+    using namespace std::chrono;
+    std::unique_lock lock(m_mutex);
+
+    random_generator uuid_gen;
+    auto now = system_clock::now();
+
+    SessionData data{userId,
+                     to_string(uuid_gen()), // 生成刷新令牌
+                     now + sessionLifetime, now + refreshInterval};
+
+    std::string access_token = to_string(uuid_gen());
+    m_sessions[access_token] = data;
+
+    m_refreshMap[data.refreshToken] = access_token;
+
+    return access_token;
+}
+
+void SessionStore::removeSession(const std::string &token) {
+    std::unique_lock lock(m_mutex);
+    auto it = m_sessions.find(token);
+    if (it != m_sessions.end()) {
+        m_refreshMap.erase(it->second.refreshToken);
+        m_sessions.erase(it);
+    }
+}
+
+std::pair<bool, std::string> SessionStore::validateAndRefresh(const std::string &token) {
+    using namespace std::chrono;
+    using namespace boost::uuids;
+    std::unique_lock lock(m_mutex);
+    auto it = m_sessions.find(token);
+    if (it == m_sessions.end()) return {false, ""};
+
+    auto now = system_clock::now();
+    SessionData &data = it->second;
+
+    if (now > data.expireTime) { // 检查是否过期
+        m_sessions.erase(it);
+        m_refreshMap.erase(data.refreshToken);
+        return {false, ""};
+    }
+
+    std::string newToken = token;
+    if (now > data.refreshTime) { // 检查是否需要刷新
+        random_generator uuid_gen;
+        newToken = to_string(uuid_gen());
+
+        // 保留刷新令牌
+        SessionData newData = data;
+        newData.refreshTime = now + minutes{15};
+
+        m_sessions.erase(it);
+        m_sessions[newToken] = newData;
+
+        m_refreshMap[data.refreshToken] = newToken;
+    }
+
+    return {true, newToken};
+}
+
+SessionData SessionStore::at(const std::string &token) {
+    SessionData ret;
+    if (m_sessions.count(token) > 0) {
+        ret = m_sessions.at(token);
+    }
+    return ret;
+}
+
+void SessionStore::startCleanupTask() {
+    using namespace std::chrono_literals;
+    m_cleanupTimer.expires_after(5min);
+    m_cleanupTimer.async_wait([this](boost::system::error_code ec) {
+        if (!ec) {
+            cleanupExpiredSessions();
+            startCleanupTask();
+        }
+    });
+}
+
+void SessionStore::cleanupExpiredSessions() {
+    using namespace std::chrono;
+    std::unique_lock lock(m_mutex);
+    auto now = system_clock::now();
+
+    for (auto it = m_sessions.begin(); it != m_sessions.end();) {
+        if (now > it->second.expireTime) {
+            m_refreshMap.erase(it->second.refreshToken);
+            it = m_sessions.erase(it);
+        } else {
+            ++it;
+        }
+    }
+}
+
+} // namespace Older
\ No newline at end of file
diff --git a/SessionStore.h b/SessionStore.h
new file mode 100644
index 0000000..4f446b5
--- /dev/null
+++ b/SessionStore.h
@@ -0,0 +1,39 @@
+#ifndef __SESSIONSTORE_H__
+#define __SESSIONSTORE_H__
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <shared_mutex>
+
+namespace Older {
+
+struct SessionData {
+    int userId;
+    std::string refreshToken;
+    std::chrono::system_clock::time_point expireTime;
+    std::chrono::system_clock::time_point refreshTime;
+};
+
+class SessionStore {
+public:
+    SessionStore(boost::asio::io_context &ioContext);
+    std::string addSession(int userId, std::chrono::minutes sessionLifetime = std::chrono::minutes{1440}, // 24小时
+                           std::chrono::minutes refreshInterval = std::chrono::minutes{15});
+    void removeSession(const std::string &token);
+    std::pair<bool, std::string> validateAndRefresh(const std::string &token);
+    SessionData at(const std::string &token);
+
+protected:
+    void startCleanupTask();
+    void cleanupExpiredSessions();
+
+private:
+    boost::asio::io_context &m_ioContext;
+    boost::asio::steady_timer m_cleanupTimer;
+    std::shared_mutex m_mutex;
+    std::unordered_map<std::string, SessionData> m_sessions;
+    std::unordered_map<std::string, std::string> m_refreshMap;
+};
+} // namespace Older
+
+#endif // __SESSIONSTORE_H__
\ No newline at end of file
diff --git a/UnitTest/Account.cpp b/UnitTest/Account.cpp
new file mode 100644
index 0000000..d770d78
--- /dev/null
+++ b/UnitTest/Account.cpp
@@ -0,0 +1,10 @@
+#include "Base/DataStructure.h"
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(SyncMessage) {
+    using namespace Older;
+    constexpr auto password = "123456";
+    auto account = Account::hashPassword(password);
+    BOOST_CHECK_EQUAL(Account::verifyPassword(account, password), true);
+    BOOST_CHECK_EQUAL(Account::verifyPassword(account, "23456"), false);
+}
\ No newline at end of file
diff --git a/UnitTest/CMakeLists.txt b/UnitTest/CMakeLists.txt
new file mode 100644
index 0000000..49936ae
--- /dev/null
+++ b/UnitTest/CMakeLists.txt
@@ -0,0 +1,11 @@
+find_package(Boost REQUIRED COMPONENTS unit_test_framework)
+
+add_executable(OlderUnitTest main.cpp
+    Account.cpp
+)
+
+target_link_libraries(OlderUnitTest
+    PRIVATE Base
+    PRIVATE Kylin::Core
+    PRIVATE Boost::unit_test_framework
+)
\ No newline at end of file
diff --git a/UnitTest/main.cpp b/UnitTest/main.cpp
new file mode 100644
index 0000000..ebf9395
--- /dev/null
+++ b/UnitTest/main.cpp
@@ -0,0 +1,2 @@
+#define BOOST_TEST_MODULE OlderTest
+#include "boost/test/included/unit_test.hpp"