Skip to content

Commit de13a99

Browse files
committed
feat: 로그인 로직 개선 및 버그 수정, 보안 문제 해결
1 parent f5d7800 commit de13a99

File tree

5 files changed

+114
-29
lines changed

5 files changed

+114
-29
lines changed

Assets/Infrastructure/Auth/AuthManager.cs

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public static AuthManager Instance
4141

4242
private bool _isInitialized = false;
4343
private bool _isAutoRefreshInProgress = false;
44+
private bool _initStarted = false;
45+
private UniTask _initTask;
4446

4547
#endregion
4648

@@ -106,15 +108,27 @@ private void Awake()
106108
{
107109
_instance = this;
108110
DontDestroyOnLoad(gameObject);
109-
InitializeAsync().Forget();
111+
_initStarted = true;
112+
_initTask = InitializeAsync();
110113
}
111114
else if (_instance != this)
112115
{
113116
Destroy(gameObject);
114117
}
115118
}
116119

117-
private async UniTaskVoid InitializeAsync()
120+
private async UniTask EnsureInitializedAsync()
121+
{
122+
if (_isInitialized) return;
123+
if (!_initStarted)
124+
{
125+
_initStarted = true;
126+
_initTask = InitializeAsync();
127+
}
128+
await _initTask;
129+
}
130+
131+
private async UniTask InitializeAsync()
118132
{
119133
try
120134
{
@@ -183,9 +197,11 @@ private void OnDestroy()
183197
/// <returns>로그인 성공 여부</returns>
184198
public async UniTask<bool> LoginAsGuestAsync()
185199
{
200+
await EnsureInitializedAsync();
201+
186202
if (!_isInitialized)
187203
{
188-
Debug.LogError("[AuthManager] AuthManager가 초기화되지 않았습니다.");
204+
Debug.LogError("[AuthManager] AuthManager 초기화에 실패했습니다.");
189205
return false;
190206
}
191207

@@ -208,9 +224,11 @@ public async UniTask<bool> LoginAsGuestAsync()
208224
/// <returns>로그인 성공 여부</returns>
209225
public async UniTask<bool> LoginWithOAuth2Async()
210226
{
227+
await EnsureInitializedAsync();
228+
211229
if (!_isInitialized)
212230
{
213-
Debug.LogError("[AuthManager] AuthManager가 초기화되지 않았습니다.");
231+
Debug.LogError("[AuthManager] AuthManager 초기화에 실패했습니다.");
214232
return false;
215233
}
216234

@@ -281,9 +299,11 @@ public void Logout()
281299
/// <returns>갱신 성공 여부</returns>
282300
public async UniTask<bool> RefreshTokenAsync()
283301
{
302+
await EnsureInitializedAsync();
303+
284304
if (!_isInitialized)
285305
{
286-
Debug.LogError("[AuthManager] AuthManager가 초기화되지 않았습니다.");
306+
Debug.LogError("[AuthManager] AuthManager 초기화에 실패했습니다.");
287307
return false;
288308
}
289309

@@ -306,9 +326,11 @@ public async UniTask<bool> RefreshTokenAsync()
306326
/// <returns>유효한 토큰 보장 여부</returns>
307327
public async UniTask<bool> EnsureValidTokenAsync(int minutesBeforeExpiry = 5)
308328
{
329+
await EnsureInitializedAsync();
330+
309331
if (!_isInitialized)
310332
{
311-
Debug.LogError("[AuthManager] AuthManager가 초기화되지 않았습니다.");
333+
Debug.LogError("[AuthManager] AuthManager 초기화에 실패했습니다.");
312334
return false;
313335
}
314336

Assets/Infrastructure/Auth/OAuth2/Utils/OAuth2CallbackParser.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public static OAuth2CallbackResult ParseCallbackUrl(string callbackUrl)
2929
var uri = new Uri(callbackUrl);
3030
var query = HttpUtility.ParseQueryString(uri.Query);
3131

32-
// 쿼리 파라미터를 Dictionary로 변환
33-
var queryParams = new Dictionary<string, string>();
32+
// 쿼리 파라미터를 Dictionary로 변환 (대소문자 무시)
33+
var queryParams = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
3434
foreach (string key in query.AllKeys)
3535
{
3636
if (!string.IsNullOrEmpty(key))
@@ -40,14 +40,13 @@ public static OAuth2CallbackResult ParseCallbackUrl(string callbackUrl)
4040
}
4141

4242
// success 파라미터 확인
43-
if (!queryParams.ContainsKey("success"))
43+
if (!queryParams.TryGetValue("success", out var successRaw) || string.IsNullOrEmpty(successRaw))
4444
{
45-
return OAuth2CallbackResult.ErrorResult("success 파라미터가 없습니다.", callbackUrl);
45+
return OAuth2CallbackResult.ErrorResult("success 파라미터가 비어있습니다.", callbackUrl);
4646
}
47+
var success = successRaw.Equals("true", StringComparison.OrdinalIgnoreCase);
4748

48-
var success = queryParams["success"].ToLower();
49-
50-
if (success == "true")
49+
if (success)
5150
{
5251
// 성공 케이스: state 파라미터 확인
5352
if (!queryParams.ContainsKey("state"))

Assets/Infrastructure/Auth/TokenManager.cs

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,13 @@ private string EncryptData(string data)
235235
using (var aes = Aes.Create())
236236
{
237237
aes.Key = Encoding.UTF8.GetBytes(ENCRYPTION_KEY.PadRight(32, '0').Substring(0, 32));
238-
aes.IV = new byte[16];
238+
aes.GenerateIV(); // 랜덤 IV 생성
239239

240240
using (var encryptor = aes.CreateEncryptor())
241241
using (var ms = new System.IO.MemoryStream())
242242
{
243+
// 먼저 IV를 기록
244+
ms.Write(aes.IV, 0, aes.IV.Length);
243245
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
244246
using (var sw = new System.IO.StreamWriter(cs))
245247
{
@@ -260,24 +262,68 @@ private string DecryptData(string encryptedData)
260262
{
261263
try
262264
{
263-
using (var aes = Aes.Create())
265+
// 새로운 방식 (랜덤 IV) 시도
266+
return DecryptDataWithRandomIV(encryptedData);
267+
}
268+
catch (Exception)
269+
{
270+
// 실패 시 기존 방식 (고정 IV) 시도
271+
try
264272
{
265-
aes.Key = Encoding.UTF8.GetBytes(ENCRYPTION_KEY.PadRight(32, '0').Substring(0, 32));
266-
aes.IV = new byte[16];
267-
268-
using (var decryptor = aes.CreateDecryptor())
269-
using (var ms = new System.IO.MemoryStream(Convert.FromBase64String(encryptedData)))
270-
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
271-
using (var sr = new System.IO.StreamReader(cs))
272-
{
273-
return sr.ReadToEnd();
274-
}
273+
Debug.LogWarning("[TokenManager] 새로운 방식 복호화 실패, 기존 방식으로 시도합니다.");
274+
return DecryptDataWithFixedIV(encryptedData);
275+
}
276+
catch (Exception ex)
277+
{
278+
Debug.LogError($"[TokenManager] 모든 복호화 방식 실패: {ex.Message}");
279+
throw;
275280
}
276281
}
277-
catch (Exception ex)
282+
}
283+
284+
private string DecryptDataWithRandomIV(string encryptedData)
285+
{
286+
using (var aes = Aes.Create())
278287
{
279-
Debug.LogError($"[TokenManager] 복호화 실패: {ex.Message}");
280-
throw;
288+
aes.Key = Encoding.UTF8.GetBytes(ENCRYPTION_KEY.PadRight(32, '0').Substring(0, 32));
289+
290+
var allBytes = Convert.FromBase64String(encryptedData);
291+
if (allBytes.Length < 16) throw new InvalidOperationException("암호문 길이가 유효하지 않습니다.");
292+
293+
// IV 추출
294+
var iv = new byte[16];
295+
Buffer.BlockCopy(allBytes, 0, iv, 0, iv.Length);
296+
297+
// 암호문 추출
298+
var cipher = new byte[allBytes.Length - iv.Length];
299+
Buffer.BlockCopy(allBytes, iv.Length, cipher, 0, cipher.Length);
300+
301+
aes.IV = iv;
302+
303+
using (var decryptor = aes.CreateDecryptor())
304+
using (var ms = new System.IO.MemoryStream(cipher))
305+
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
306+
using (var sr = new System.IO.StreamReader(cs))
307+
{
308+
return sr.ReadToEnd();
309+
}
310+
}
311+
}
312+
313+
private string DecryptDataWithFixedIV(string encryptedData)
314+
{
315+
using (var aes = Aes.Create())
316+
{
317+
aes.Key = Encoding.UTF8.GetBytes(ENCRYPTION_KEY.PadRight(32, '0').Substring(0, 32));
318+
aes.IV = new byte[16]; // 기존 고정 IV
319+
320+
using (var decryptor = aes.CreateDecryptor())
321+
using (var ms = new System.IO.MemoryStream(Convert.FromBase64String(encryptedData)))
322+
using (var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
323+
using (var sr = new System.IO.StreamReader(cs))
324+
{
325+
return sr.ReadToEnd();
326+
}
281327
}
282328
}
283329

Assets/Infrastructure/Network/DTOs/Auth/GuestLoginResponse.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public TokenSet ToTokenSet()
4444
return null;
4545
}
4646

47+
if (string.IsNullOrEmpty(Tokens.AccessToken))
48+
{
49+
// 서버 응답에 액세스 토큰이 없으면 TokenSet 생성 불가
50+
return null;
51+
}
4752
var accessToken = new AccessToken(Tokens.AccessToken);
4853

4954
RefreshToken refreshToken = null;

Assets/Infrastructure/Network/Http/HttpApiClient.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,19 @@ public async UniTask<T> GetAsync<T>(string endpoint, Dictionary<string, string>
118118
return await SendJsonRequestWithHeadersAsync<T>(url, UnityWebRequest.kHttpVerbGET, null, headers, cancellationToken);
119119
}
120120

121+
/// <summary>
122+
/// POST 요청 (HTTP 헤더 포함 응답)
123+
/// </summary>
124+
public async UniTask<(T Data, Dictionary<string, string> Headers)> PostWithHeadersAsync<T>(string endpoint, object data = null, Dictionary<string, string> headers = null, bool requiresAuth = false, CancellationToken cancellationToken = default)
125+
{
126+
EnsureInitialized();
127+
EnsureAuthToken(requiresAuth);
128+
129+
var url = GetFullUrl(endpoint);
130+
var jsonData = SerializeData(data);
131+
return await SendJsonRequestWithHeadersAsync<T>(url, UnityWebRequest.kHttpVerbPOST, jsonData, headers, cancellationToken);
132+
}
133+
121134
public async UniTask<T> PostAsync<T>(string endpoint, object data = null, Dictionary<string, string> headers = null, bool requiresAuth = false, CancellationToken cancellationToken = default)
122135
{
123136
EnsureInitialized();
@@ -384,7 +397,7 @@ private WWWForm CreateFormData(Dictionary<string, object> formData, Dictionary<s
384397
}
385398
else
386399
{
387-
form.AddField(kvp.Key, kvp.Value.ToString());
400+
form.AddField(kvp.Key, kvp.Value?.ToString() ?? string.Empty);
388401
}
389402
}
390403

0 commit comments

Comments
 (0)