forked from AcaMate/AcaMate_Web
[✨] 로그인 동작 및 Repository 동작
1. 쿠키만 사용하던 저장 동작을 다른 저장소도 사용하게 변경 1.1. 쿠키 대신 세션 레포지토리를 기본적으로 사용하게 Service 코드 구현 2. 로그인 되었을 경우 화면 표기 변경 2.1. 시작하기 버튼 hidden 처리 2.2. 사용자 이름 불러오기 2.3. 로그인 동작 관련 변수 스토리지 저장 2.4. 서버에서 직접적인 크라이언트 쿠키 저장이 아닌 서버는 뒤의 값으로 간섭하게 변경
This commit is contained in:
parent
3ffec93958
commit
9621169d57
|
@ -8,17 +8,17 @@ namespace Front;
|
|||
public partial class App : ComponentBase
|
||||
{
|
||||
[Inject] private APIService API { get; set; } = default!;
|
||||
[Inject] private CookieService Cookie { get; set; } = default!;
|
||||
[Inject] private StorageService StorageService { get; set; } = default!;
|
||||
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var cookie = await Cookie.GetCookieAsync("Web_AM_Connect_Key");
|
||||
var headerValue = await StorageService.GetItemAsync("Web_AM_Connect_Key");
|
||||
|
||||
// 값 없으면 API 호출
|
||||
if (string.IsNullOrEmpty(cookie))
|
||||
if (string.IsNullOrEmpty(headerValue))
|
||||
{
|
||||
var response = await API.GetJsonAsync<APIHeader, AppHeader>(
|
||||
"/api/v1/in/app",
|
||||
|
@ -30,7 +30,7 @@ public partial class App : ComponentBase
|
|||
});
|
||||
if (!string.IsNullOrEmpty(response.data.header))
|
||||
{
|
||||
await Cookie.SetCookieAsync("Web_AM_Connect_Key", response.data.header);
|
||||
await StorageService.SetItemAsync("Web_AM_Connect_Key", response.data.header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,15 @@ using Microsoft.Extensions.Configuration;
|
|||
|
||||
using Front;
|
||||
using Front.Program.Services;
|
||||
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
// 설정 파일 로드
|
||||
// builder.Configuration.AddJsonFile("appsettings.json", optional: false);
|
||||
builder.Configuration.AddJsonFile($"appsettings.{builder.HostEnvironment.Environment}.json", optional: true);
|
||||
|
||||
builder.Services.AddScoped(sp => //new HttpClient
|
||||
{
|
||||
|
@ -30,7 +33,8 @@ builder.Services.AddScoped(sp => //new HttpClient
|
|||
|
||||
// SCOPED 으로 등록된 서비스는 DI 컨테이너에 등록된 서비스의 인스턴스를 사용합니다.
|
||||
builder.Services.AddScoped<APIService>();
|
||||
builder.Services.AddScoped<CookieService>();
|
||||
builder.Services.AddScoped<SecureService>();
|
||||
builder.Services.AddScoped<StorageService>();
|
||||
// builder.Services.AddRazorPages();
|
||||
// builder.Services.AddServerSideBlazor();
|
||||
builder.Services.AddScoped<LoadingService>();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
using Front.Program.Views.Project;
|
||||
using Front.Program.Services;
|
||||
|
||||
|
@ -13,6 +14,9 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
|
|||
[Inject]
|
||||
LoadingService LoadingService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
StorageService StorageService { get; set; } = default!;
|
||||
|
||||
// 경로의 시작 부분
|
||||
// protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("auth", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
|
@ -23,11 +27,43 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
|
|||
{
|
||||
LoadingService.OnChange += StateHasChanged;
|
||||
Navigation.LocationChanged += HandleLocationChanged;
|
||||
HandleLocationChanged(this, new LocationChangedEventArgs(Navigation.Uri, false));
|
||||
}
|
||||
|
||||
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
private async void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
LoadingService.HideNavigationLoading();
|
||||
|
||||
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
|
||||
Console.WriteLine($"리다이렉트된 URI: {uri}");
|
||||
|
||||
if (uri.Query.Contains("auth="))
|
||||
{
|
||||
var query = uri.Query.TrimStart('?');
|
||||
var parameters = query.Split('&')
|
||||
.Select(p => p.Split('='))
|
||||
.Where(p => p.Length == 2)
|
||||
.ToDictionary(p => p[0], p => p[1]);
|
||||
|
||||
if (parameters.TryGetValue("auth", out var auth))
|
||||
{
|
||||
Console.WriteLine($"auth 파라미터 값: {auth}");
|
||||
if (auth == "true")
|
||||
{
|
||||
await StorageService.SetItemAsync("IsLogin", "true");
|
||||
Console.WriteLine("로그인 상태를 true로 설정했습니다.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await StorageService.RemoveItemAsync("IsLogin");
|
||||
Console.WriteLine("로그인 상태를 제거했습니다.");
|
||||
}
|
||||
|
||||
// 파라미터를 제거하고 리다이렉트
|
||||
var baseUri = uri.GetLeftPart(UriPartial.Path);
|
||||
Navigation.NavigateTo(baseUri, forceLoad: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
@ -28,4 +28,15 @@ public class APIService
|
|||
var response = await _http.GetFromJsonAsync<APIResponseStatus<TResponse>>($"{url}?{parameter}");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
dynamic 타입을 사용하면:
|
||||
타입 안전성이 떨어집니다
|
||||
컴파일 타임에 오류를 잡을 수 없습니다
|
||||
런타임에 예상치 못한 오류가 발생할 수 있습니다
|
||||
현재 Register.razor.cs에서 JsonElement를 사용하는 방식이 더 안전할 수 있습니다. 왜냐하면:
|
||||
JSON 응답의 구조를 명시적으로 확인할 수 있습니다 (TryGetProperty 사용)
|
||||
각 속성의 타입을 명확하게 처리할 수 있습니다
|
||||
예상치 못한 데이터 구조에 대해 더 안전하게 대응할 수 있습니다
|
||||
*/
|
|
@ -1,29 +0,0 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Front.Program.Services;
|
||||
|
||||
public class CookieService
|
||||
{
|
||||
private readonly IJSRuntime _js;
|
||||
|
||||
public CookieService(IJSRuntime js)
|
||||
{
|
||||
_js = js;
|
||||
}
|
||||
|
||||
public async Task SetCookieAsync(string key, string value)
|
||||
{
|
||||
await _js.InvokeVoidAsync("setCookie", key, value, 1);
|
||||
}
|
||||
|
||||
public async Task<string?> GetCookieAsync(string key)
|
||||
{
|
||||
return await _js.InvokeAsync<string>("getCookie", key);
|
||||
}
|
||||
|
||||
public async Task DeleteCookieAsync(string key)
|
||||
{
|
||||
await _js.InvokeVoidAsync("deleteCookie", key);
|
||||
}
|
||||
}
|
120
Program/Services/SecureService.cs
Normal file
120
Program/Services/SecureService.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
|
||||
namespace Front.Program.Services;
|
||||
|
||||
public class SecureService
|
||||
{
|
||||
private readonly ILogger<SecureService> _logger;
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private readonly IWebAssemblyHostEnvironment _environment;
|
||||
private string? _key;
|
||||
private string? _iv;
|
||||
private Task? _initializationTask;
|
||||
|
||||
public SecureService(
|
||||
ILogger<SecureService> logger,
|
||||
IJSRuntime jsRuntime,
|
||||
IWebAssemblyHostEnvironment environment)
|
||||
{
|
||||
_logger = logger;
|
||||
_jsRuntime = jsRuntime;
|
||||
_environment = environment;
|
||||
_initializationTask = InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var configFile = $"appsettings.{_environment.Environment}.json";
|
||||
_logger.LogInformation($"설정 파일 로드: {configFile}");
|
||||
|
||||
var config = await _jsRuntime.InvokeAsync<JsonElement>("loadConfig", configFile);
|
||||
if (config.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
throw new InvalidOperationException($"설정 파일을 로드할 수 없습니다: {configFile}");
|
||||
}
|
||||
|
||||
var security = config.GetProperty("Security");
|
||||
_key = security.GetProperty("EncryptionKey").GetString()
|
||||
?? throw new ArgumentNullException("Security:EncryptionKey");
|
||||
_iv = security.GetProperty("EncryptionIV").GetString()
|
||||
?? throw new ArgumentNullException("Security:EncryptionIV");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"설정 로드 중 오류 발생: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureInitializedAsync()
|
||||
{
|
||||
if (_initializationTask != null)
|
||||
{
|
||||
await _initializationTask;
|
||||
_initializationTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetKeyBytes(string key)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
return sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetIVBytes(string iv)
|
||||
{
|
||||
using (var sha256 = SHA256.Create())
|
||||
{
|
||||
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(iv));
|
||||
return hash.Take(16).ToArray(); // IV는 16바이트만 필요
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> EncryptAsync(string plainText)
|
||||
{
|
||||
await EnsureInitializedAsync();
|
||||
|
||||
if (_key == null || _iv == null)
|
||||
throw new InvalidOperationException("암호화 키가 초기화되지 않았습니다.");
|
||||
|
||||
try
|
||||
{
|
||||
return await _jsRuntime.InvokeAsync<string>("encryptText", plainText, _key, _iv);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"암호화 중 오류 발생: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> DecryptAsync(string cipherText)
|
||||
{
|
||||
await EnsureInitializedAsync();
|
||||
|
||||
if (_key == null || _iv == null)
|
||||
throw new InvalidOperationException("암호화 키가 초기화되지 않았습니다.");
|
||||
|
||||
try
|
||||
{
|
||||
return await _jsRuntime.InvokeAsync<string>("decryptText", cipherText, _key, _iv);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"복호화 중 오류 발생: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// 동기 메서드는 비동기 메서드를 호출하도록 수정
|
||||
public string Encrypt(string plainText) => EncryptAsync(plainText).GetAwaiter().GetResult();
|
||||
public string Decrypt(string cipherText) => DecryptAsync(cipherText).GetAwaiter().GetResult();
|
||||
}
|
75
Program/Services/StorageService.cs
Normal file
75
Program/Services/StorageService.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Front.Program.Services;
|
||||
|
||||
public enum STORAGE_TYPE
|
||||
{
|
||||
Cookie,
|
||||
Local,
|
||||
Session
|
||||
}
|
||||
|
||||
public class StorageService
|
||||
{
|
||||
private readonly IJSRuntime _js;
|
||||
|
||||
public StorageService(IJSRuntime js)
|
||||
{
|
||||
_js = js;
|
||||
}
|
||||
|
||||
public async Task SetItemAsync(string key, string value, STORAGE_TYPE type = STORAGE_TYPE.Session, int expire_time = 1)
|
||||
{
|
||||
await (type switch
|
||||
{
|
||||
STORAGE_TYPE.Cookie => _js.InvokeVoidAsync("setCookie", key, value, expire_time),
|
||||
STORAGE_TYPE.Local => _js.InvokeVoidAsync("localStorage.setItem", key, value),
|
||||
STORAGE_TYPE.Session => _js.InvokeVoidAsync("sessionStorage.setItem", key, value),
|
||||
_ => throw new ArgumentException("Invalid storage type")
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<string?> GetItemAsync(string key, STORAGE_TYPE type = STORAGE_TYPE.Session)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
STORAGE_TYPE.Cookie => await _js.InvokeAsync<string>("getCookie", key),
|
||||
STORAGE_TYPE.Local => await _js.InvokeAsync<string>("localStorage.getItem", key),
|
||||
STORAGE_TYPE.Session => await _js.InvokeAsync<string>("sessionStorage.getItem", key),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task RemoveItemAsync(string key, STORAGE_TYPE type = STORAGE_TYPE.Session)
|
||||
{
|
||||
await (type switch
|
||||
{
|
||||
STORAGE_TYPE.Cookie => _js.InvokeVoidAsync("deleteCookie", key),
|
||||
STORAGE_TYPE.Local => _js.InvokeVoidAsync("localStorage.removeItem", key),
|
||||
STORAGE_TYPE.Session => _js.InvokeVoidAsync("sessionStorage.removeItem", key),
|
||||
_ => throw new ArgumentException("Invalid storage type")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* 스토리지 종류
|
||||
1. Cookie
|
||||
만료일 : 명시적으로 설정이 가능 (설정에 따라 그 값을 변경 가능)
|
||||
접근성 : 서버와 클라이언트 모두 접근이 가능함
|
||||
특징
|
||||
1. HTTP 요청마다 서버로 전송이 되며 도메인/경로 제한이 가능하다.
|
||||
2. HttpOnly나 secure 옵션으로 보안 설정이 가능하다.
|
||||
2. Local
|
||||
만료일 : 브라우저 데이터 삭제 전까지는 영구 보관 가능
|
||||
접근성 : 클라이언트 측에서 JS 를 통해서만 접근이 가능
|
||||
특징
|
||||
1. 도메인별로 분리된 저장소로 동기적인 동작이 된다.
|
||||
2. 문자열만 저장이 가능하다.
|
||||
3. Sesson
|
||||
만료일 : 브라우저의 탭/창 닫을 때 까지 보관 (닫게 되면 삭제)
|
||||
접근성 : 클라이언트 측에서 JS 를 통해서만 접근이 가능
|
||||
특징
|
||||
1. 탭/창 별로 불리된 저장소며 동기적인 동작이 가능하다.
|
||||
2. 문자열만 저장이 가능하다.
|
||||
*/
|
|
@ -16,6 +16,7 @@ public partial class Register : ComponentBase
|
|||
[Inject] private HttpClient Http { get; set; } = default!;
|
||||
[Inject] private IConfiguration Configuration { get; set; } = default!;
|
||||
[Inject] private LoadingService LoadingService { get; set; } = default!;
|
||||
[Inject] private StorageService CookieService { get; set; } = default!;
|
||||
|
||||
private ElementReference dateInputRef;
|
||||
|
||||
|
@ -39,8 +40,7 @@ public partial class Register : ComponentBase
|
|||
objRef = DotNetObjectReference.Create(this);
|
||||
try
|
||||
{
|
||||
// 쿠키에서 토큰 가져오기
|
||||
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1]");
|
||||
var token = await CookieService.GetItemAsync("Web_AM_Connect_Key");
|
||||
if (string.IsNullOrEmpty(token))
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "인증 정보가 없습니다.");
|
||||
|
@ -169,8 +169,7 @@ public partial class Register : ComponentBase
|
|||
try
|
||||
{
|
||||
LoadingService.ShowLoading();
|
||||
// 쿠키에서 토큰 가져오기
|
||||
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1] || ''");
|
||||
var token = await CookieService.GetItemAsync("Web_AM_Connect_Key");
|
||||
Console.WriteLine($"쿠키에서 가져온 토큰: '{token}'");
|
||||
|
||||
if (string.IsNullOrEmpty(token))
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a>
|
||||
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
|
||||
</div>
|
||||
@if (!isLoggedIn)
|
||||
@if (!isLogin)
|
||||
{
|
||||
<button class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em] mr-4"
|
||||
@onclick="OnClickLogin">
|
||||
|
@ -38,7 +38,7 @@
|
|||
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/about" @onclick="() => isOpen = !isOpen">About</a>
|
||||
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a>
|
||||
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
|
||||
@if (!isLoggedIn)
|
||||
@if (!isLogin)
|
||||
{
|
||||
<button class="flex w-full cursor-pointer items-center justify-center rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em]"
|
||||
@onclick="OnClickLogin">
|
||||
|
|
|
@ -1,24 +1,123 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Front.Program.Services;
|
||||
|
||||
namespace Front.Program.Views.Project;
|
||||
|
||||
public partial class TopProjectNav : ComponentBase
|
||||
{
|
||||
[Inject]
|
||||
NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Inject] StorageService StorageService { get; set; } = default!;
|
||||
[Inject] SecureService SecureService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
IJSRuntime JS { get; set; } = default!;
|
||||
[Inject] IJSRuntime JS { get; set; } = default!;
|
||||
|
||||
[Inject] HttpClient Http { get; set; } = default!;
|
||||
string UserName { get; set; } = default!;
|
||||
|
||||
protected bool isOpen = false;
|
||||
protected bool isLoggedIn = false;
|
||||
bool isLogin = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// 쿠키에서 로그인 상태 확인
|
||||
var isLoginCookie = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('IsLogin='))?.split('=')[1]");
|
||||
isLoggedIn = isLoginCookie == "true";
|
||||
Console.WriteLine("TopNAV 확인");
|
||||
isLogin = isLogin = bool.TryParse(await StorageService.GetItemAsync("IsLogin"), out var result) && result;
|
||||
if (isLogin) return;
|
||||
try
|
||||
{
|
||||
|
||||
var encryptedName = await StorageService.GetItemAsync("USER");
|
||||
Console.WriteLine($"{encryptedName}");
|
||||
if (!string.IsNullOrEmpty(encryptedName))
|
||||
{
|
||||
try
|
||||
{
|
||||
UserName = await SecureService.DecryptAsync(encryptedName);
|
||||
Console.WriteLine($"세션 스토리지에서 가져온 사용자 이름: '{UserName}'");
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"이름 복호화 중 오류 발생: {ex.Message}");
|
||||
await StorageService.RemoveItemAsync("USER");
|
||||
}
|
||||
}
|
||||
|
||||
// apiSender.js의 함수를 사용하여 연결 키 가져오기
|
||||
var headerValue = await StorageService.GetItemAsync("Web_AM_Connect_Key");
|
||||
Console.WriteLine($"세션 스토리지에서 가져온 헤더 값: '{headerValue}'");
|
||||
|
||||
if (string.IsNullOrEmpty(headerValue))
|
||||
{
|
||||
Console.WriteLine("연결 키가 없습니다");
|
||||
return;
|
||||
}
|
||||
|
||||
// apiSender.js의 함수를 사용하여 API 호출
|
||||
Console.WriteLine("세션 API 호출 시작");
|
||||
var response = await JS.InvokeAsync<JsonElement>("fetchWithHeaderAndReturnUrl",
|
||||
"/api/v1/in/user/auth/session",
|
||||
"GET",
|
||||
"Web_AM_Connect_Key",
|
||||
headerValue);
|
||||
|
||||
Console.WriteLine($"세션 API 응답 타입: {response.ValueKind}");
|
||||
Console.WriteLine($"세션 API 응답 내용: {response}");
|
||||
|
||||
if (response.ValueKind == JsonValueKind.Null || response.ValueKind == JsonValueKind.Undefined)
|
||||
{
|
||||
Console.WriteLine("응답이 null이거나 undefined입니다");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (response.TryGetProperty("status", out var statusElement))
|
||||
{
|
||||
Console.WriteLine($"status 요소 타입: {statusElement.ValueKind}");
|
||||
if (statusElement.TryGetProperty("code", out var codeElement))
|
||||
{
|
||||
var code = codeElement.GetString();
|
||||
Console.WriteLine($"응답 코드: {code}");
|
||||
|
||||
if (code == "000")
|
||||
{
|
||||
if (response.TryGetProperty("data", out var dataElement))
|
||||
{
|
||||
Console.WriteLine($"data 요소 타입: {dataElement.ValueKind}");
|
||||
if (dataElement.TryGetProperty("name", out var nameElement))
|
||||
{
|
||||
UserName = nameElement.GetString() ?? "이름 없음";
|
||||
isLogin = true;
|
||||
Console.WriteLine($"NM: {UserName}");
|
||||
var encryptedUserName = await SecureService.EncryptAsync(UserName);
|
||||
Console.WriteLine($"NM: {encryptedUserName}");
|
||||
await StorageService.SetItemAsync("USER", encryptedUserName);
|
||||
await StorageService.SetItemAsync("Web_AM_Connect_Key", headerValue);
|
||||
Console.WriteLine($"로그인된 사용자: {UserName}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Console.WriteLine("로그인되지 않은 상태");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"응답 처리 중 오류 발생: {ex.Message}");
|
||||
Console.WriteLine($"응답 처리 스택 트레이스: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"세션 확인 중 오류 발생: {ex.Message}");
|
||||
Console.WriteLine($"스택 트레이스: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClickMenuDown()
|
||||
|
|
13
appsettings.json
Normal file
13
appsettings.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Security": {
|
||||
"EncryptionKey": "AcaMate2025SecureKeySeanForEncryption19940509",
|
||||
"EncryptionIV": "AcaMate2025IV9459"
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
{
|
||||
"ApiBaseUrl": "https://devacamate.ipstein.myds.me/"
|
||||
"ApiBaseUrl": "https://devacamate.ipstein.myds.me/",
|
||||
"Security": {
|
||||
"EncryptionKey": "AcaMate2025SecureKeyForEncryptionSean1994",
|
||||
"EncryptionIV": "AcaMate2025IV9459"
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
{
|
||||
"ApiBaseUrl": "https://acamate.ipstein.myds.me/"
|
||||
"ApiBaseUrl": "https://acamate.ipstein.myds.me/",
|
||||
"Security": {
|
||||
"EncryptionKey": "AcaMate2025SecureKeyForEncryptionSean1994",
|
||||
"EncryptionIV": "AcaMate2025IV9459"
|
||||
}
|
||||
}
|
|
@ -70,6 +70,7 @@
|
|||
const v = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
|
||||
return v ? v.pop() : '';
|
||||
};
|
||||
|
||||
window.setCookie = function (name, value, days) {
|
||||
let expires = "";
|
||||
if (days) {
|
||||
|
|
|
@ -12,12 +12,85 @@ window.postWithHeader = function(url, method, headerKey, headerValue) {
|
|||
};
|
||||
|
||||
window.fetchWithHeaderAndReturnUrl = async function(url, method, headerKey, headerValue) {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
[headerKey]: headerValue
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
headers: {
|
||||
[headerKey]: headerValue
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('API 호출 실패:', response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
return data.url;
|
||||
};
|
||||
|
||||
const data = await response.json();
|
||||
console.log('API 응답 데이터:', data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API 호출 중 오류 발생:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
window.loadConfig = async function(configFile) {
|
||||
try {
|
||||
console.log('설정 파일 로드 시도:', configFile);
|
||||
const response = await fetch(configFile);
|
||||
if (!response.ok) {
|
||||
console.error('설정 파일 로드 실패:', response.status, response.statusText);
|
||||
return null;
|
||||
}
|
||||
const config = await response.json();
|
||||
console.log('설정 파일 로드 성공:', configFile);
|
||||
return config;
|
||||
} catch (error) {
|
||||
console.error('설정 파일 로드 중 오류 발생:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
window.encryptText = async function(text, key, iv) {
|
||||
try {
|
||||
// XOR 암호화 구현
|
||||
let result = '';
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
|
||||
result += String.fromCharCode(charCode);
|
||||
}
|
||||
|
||||
// UTF-8로 인코딩 후 Base64로 변환
|
||||
const utf8Encoder = new TextEncoder();
|
||||
const bytes = utf8Encoder.encode(result);
|
||||
const base64 = btoa(String.fromCharCode.apply(null, bytes));
|
||||
return base64;
|
||||
} catch (error) {
|
||||
console.error('암호화 중 오류 발생:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
window.decryptText = async function(encryptedText, key, iv) {
|
||||
try {
|
||||
// Base64 디코딩 후 UTF-8 디코딩
|
||||
const binary = atob(encryptedText);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
const utf8Decoder = new TextDecoder();
|
||||
const text = utf8Decoder.decode(bytes);
|
||||
|
||||
// XOR 복호화
|
||||
let result = '';
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const charCode = text.charCodeAt(i) ^ key.charCodeAt(i % key.length);
|
||||
result += String.fromCharCode(charCode);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('복호화 중 오류 발생:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue
Block a user