diff --git a/Assets/Scripts/Online/Logic/AuthService.cs b/Assets/Scripts/Online/Logic/AuthService.cs
new file mode 100644
index 00000000..f28543cf
--- /dev/null
+++ b/Assets/Scripts/Online/Logic/AuthService.cs
@@ -0,0 +1,327 @@
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using IchniOnline.Online.Network;
+using IchniOnline.Online.Network.Models;
+using TapSDK.Login;
+using UnityEngine;
+
+namespace IchniOnline.Online.Network.Models
+{
+ [Serializable]
+ public class SessionKeyResponseDto
+ {
+ public string SessionKey;
+ public long ExpiresAt;
+ }
+}
+
+namespace IchniOnline.Online.Logic
+{
+ ///
+ /// Authentication orchestration service that coordinates TapTap login,
+ /// password login, registration, and logout flows.
+ /// Pure orchestration — no UI state management. Results are delivered via events.
+ ///
+ public static class IchniOnlineAuthService
+ {
+ ///
+ /// 登录成功时触发,参数为服务端返回的登录响应(Register 成功时可能为 null)
+ ///
+ public static event Action OnLoginSuccess;
+
+ ///
+ /// 登录失败时触发,参数为错误信息
+ ///
+ public static event Action OnLoginFailed;
+
+ ///
+ /// 登录被用户取消时触发
+ ///
+ public static event Action OnLoginCanceled;
+
+ ///
+ /// 是否正在进行登录流程,用于防止并发登录请求
+ ///
+ public static bool IsLoggingIn { get; private set; }
+
+ #region TapTap Login
+
+ ///
+ /// 使用 TapTap 登录的完整流程:
+ /// TapTap SDK → 第三方登录 API → JWT → 缓存 → 事件
+ ///
+ public static async void LoginWithTapTap()
+ {
+ if (IsLoggingIn)
+ {
+ Debug.LogWarning("[IchniOnlineAuthService] 已有登录流程正在进行中");
+ return;
+ }
+
+ IsLoggingIn = true;
+
+ Action onSuccess = null;
+ Action onCanceled = null;
+ Action onFailed = null;
+
+ onSuccess = async account =>
+ {
+ UnsubscribeTapTapEvents(onSuccess, onCanceled, onFailed);
+
+ if (account?.accessToken == null)
+ {
+ IsLoggingIn = false;
+ OnLoginFailed?.Invoke("TapTap 登录成功但 accessToken 为空");
+ return;
+ }
+
+ var dto = new ThirdPartyLoginRequestDto
+ {
+ Token = account.accessToken.kid,
+ TokenType = account.accessToken.tokenType,
+ MacKey = account.accessToken.macKey,
+ MacAlgorithm = account.accessToken.macAlgorithm
+ };
+
+ try
+ {
+ var result = await IchniOnlineApiClient.Instance.PostAsync("/api/auth/third-party/login", dto);
+
+ IsLoggingIn = false;
+
+ if (result.IsSuccess)
+ {
+ LoginCacheManager.SaveAuthSession(result.Data.Token, result.Data);
+ IchniOnlineApiClient.Instance.JwtToken = result.Data.Token;
+ OnLoginSuccess?.Invoke(result.Data);
+ }
+ else
+ {
+ string errorMessage = $"第三方登录 API 失败: {result.Message}";
+ if (!string.IsNullOrEmpty(result.ErrorDetail))
+ errorMessage += $" ({result.ErrorDetail})";
+ OnLoginFailed?.Invoke(errorMessage);
+ }
+ }
+ catch (Exception ex)
+ {
+ IsLoggingIn = false;
+ Debug.LogError($"[IchniOnlineAuthService] TapTap 登录 API 异常: {ex}");
+ OnLoginFailed?.Invoke($"TapTap 登录 API 异常: {ex.Message}");
+ }
+ };
+
+ onCanceled = () =>
+ {
+ UnsubscribeTapTapEvents(onSuccess, onCanceled, onFailed);
+ IsLoggingIn = false;
+ OnLoginCanceled?.Invoke();
+ };
+
+ onFailed = errorMessage =>
+ {
+ UnsubscribeTapTapEvents(onSuccess, onCanceled, onFailed);
+ IsLoggingIn = false;
+ OnLoginFailed?.Invoke($"TapTap 登录失败: {errorMessage}");
+ };
+
+ ThirdPartyServiceManager.Instance.OnLoginSuccess += onSuccess;
+ ThirdPartyServiceManager.Instance.OnLoginCanceled += onCanceled;
+ ThirdPartyServiceManager.Instance.OnLoginFailed += onFailed;
+
+ ThirdPartyServiceManager.Instance.StartTapTapLogin();
+ }
+
+ private static void UnsubscribeTapTapEvents(
+ Action onSuccess,
+ Action onCanceled,
+ Action onFailed)
+ {
+ if (ThirdPartyServiceManager.Instance != null)
+ {
+ ThirdPartyServiceManager.Instance.OnLoginSuccess -= onSuccess;
+ ThirdPartyServiceManager.Instance.OnLoginCanceled -= onCanceled;
+ ThirdPartyServiceManager.Instance.OnLoginFailed -= onFailed;
+ }
+ }
+
+ #endregion
+
+ #region Password Login
+
+ ///
+ /// 使用用户名和密码登录的完整流程:
+ /// 获取 session-key → XOR 加密密码 → 登录 API → JWT → 缓存 → 事件
+ ///
+ public static async void LoginWithPassword(string username, string password)
+ {
+ if (IsLoggingIn)
+ {
+ Debug.LogWarning("[IchniOnlineAuthService] 已有登录流程正在进行中");
+ return;
+ }
+
+ if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
+ {
+ OnLoginFailed?.Invoke("用户名或密码不能为空");
+ return;
+ }
+
+ IsLoggingIn = true;
+
+ try
+ {
+ // 1. 获取 session-key
+ var sessionResult = await IchniOnlineApiClient.Instance.GetAsync("/api/auth/session-key");
+ if (!sessionResult.IsSuccess)
+ {
+ IsLoggingIn = false;
+ string errorMessage = $"获取 session-key 失败: {sessionResult.Message}";
+ if (!string.IsNullOrEmpty(sessionResult.ErrorDetail))
+ errorMessage += $" ({sessionResult.ErrorDetail})";
+ OnLoginFailed?.Invoke(errorMessage);
+ return;
+ }
+
+ string sessionKey = sessionResult.Data.SessionKey;
+
+ // 2. XOR 加密密码
+ string encryptedPassword = EncryptPassword(password, sessionKey);
+
+ // 3. 调用登录 API
+ var loginDto = new LoginRequestDto
+ {
+ Username = username,
+ EncryptedPassword = encryptedPassword,
+ SessionKey = sessionKey
+ };
+
+ var loginResult = await IchniOnlineApiClient.Instance.PostAsync("/api/auth/login", loginDto);
+
+ IsLoggingIn = false;
+
+ if (loginResult.IsSuccess)
+ {
+ LoginCacheManager.SaveAuthSession(loginResult.Data.Token, loginResult.Data);
+ IchniOnlineApiClient.Instance.JwtToken = loginResult.Data.Token;
+ OnLoginSuccess?.Invoke(loginResult.Data);
+ }
+ else
+ {
+ string errorMessage = $"登录失败: {loginResult.Message}";
+ if (!string.IsNullOrEmpty(loginResult.ErrorDetail))
+ errorMessage += $" ({loginResult.ErrorDetail})";
+ OnLoginFailed?.Invoke(errorMessage);
+ }
+ }
+ catch (Exception ex)
+ {
+ IsLoggingIn = false;
+ Debug.LogError($"[IchniOnlineAuthService] 密码登录异常: {ex}");
+ OnLoginFailed?.Invoke($"登录异常: {ex.Message}");
+ }
+ }
+
+ #endregion
+
+ #region Register
+
+ ///
+ /// 用户注册流程:POST /api/auth/register
+ /// 注册成功后触发 OnLoginSuccess(null)(注册不返回 JWT)
+ ///
+ public static async void Register(string username, string password, string displayName)
+ {
+ if (IsLoggingIn)
+ {
+ Debug.LogWarning("[IchniOnlineAuthService] 已有登录流程正在进行中");
+ return;
+ }
+
+ if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
+ {
+ OnLoginFailed?.Invoke("用户名或密码不能为空");
+ return;
+ }
+
+ IsLoggingIn = true;
+
+ try
+ {
+ var registerDto = new RegisterRequestDto
+ {
+ Username = username,
+ Password = password,
+ DisplayName = displayName
+ };
+
+ var result = await IchniOnlineApiClient.Instance.PostAsync