ISecureStorage
인증 토큰처럼 민감한 문자열 값을 기기의 보안 저장소에 암호화해 보관하는 키-값 저장소입니다. 키로 값을 저장, 조회, 삭제하는 메서드와 이 앱이 저장한 값을 모두 삭제하는 메서드를 제공합니다. 저장한 값은 앱을 다시 시작해도 유지됩니다.
- 인터페이스:
ISecureStorage - 네임스페이스:
Hive.Axyl.Storage - 패키지:
com.com2usplatform.hiveaxyl.storage
등록과 획득
using Hive.Axyl.Core;
using Hive.Axyl.Core.Unity; // HiveBootstrap
using Hive.Axyl.Storage;
var config = CoreConfig.CreateBuilder("{appId}").Build();
HiveBootstrap.Initialize(config, builder =>
{
builder.AddSecureStorage();
});
if (HiveCore.TryResolve<ISecureStorage>(out var storage))
{
// 지원 플랫폼으로 빌드한 플레이어에서만 실행되는 코드
}
Unity 에디터에서는 등록되지 않습니다
ISecureStorage는 Android, Windows, iOS, macOS로 빌드한 플레이어에서만 등록됩니다. Unity 에디터와 그 밖의 플랫폼에서는 등록되지 않으므로, HiveCore.Resolve<T>()를 쓰면 RegistrationNotFoundException이 발생합니다. 반드시 TryResolve<T>()로 확인한 뒤 사용하세요.
플랫폼별 저장 방식
값은 플랫폼의 보안 저장소에 암호화되어 저장되며, 초기화에 사용한 App ID별로 구분됩니다. App ID를 바꾸면 이전 App ID로 저장한 값은 읽을 수 없습니다.
| 플랫폼 | 저장 위치 | 암호화 방식 |
|---|---|---|
| Android | 앱 전용 데이터 영역의 Jetpack DataStore 파일 | Android Keystore 키로 보호한 Tink 키로 AES-256-GCM 암호화합니다. |
| iOS, macOS | Keychain | Keychain이 암호화합니다. 기기를 재시동한 뒤 처음 잠금 해제한 이후부터 접근할 수 있고, 다른 기기로 옮겨지지 않습니다. |
| Windows | %APPDATA%\HiveAxyl 아래의 App ID별 폴더 | Windows Data Protection API(DPAPI)로 현재 Windows 사용자 계정 기준으로 암호화합니다. 파일 이름에는 키 대신 키의 SHA-256 해시를 사용합니다. |
플랫폼마다 아래 사항을 확인하세요.
iOS, macOS
- 앱을 삭제한 뒤에도 남는 값: Keychain에 저장한 값은 앱을 삭제해도 남아 있을 수 있으므로, 앱을 다시 설치한 뒤 읽은 인증 정보는 그대로 신뢰하지 말고 유효한지 확인하세요.
- 기기 재시동 후 첫 잠금 해제 전의 호출:
AccessDenied반환
빌드할 때 앱 타깃에 Keychain Sharing 엔타이틀먼트가 자동으로 추가됩니다. 이 엔타이틀먼트 없이 서명한 앱에서 호출하면 Code가 FailedPrecondition인 Failure를 반환합니다. Xcode 프로젝트를 만들지 않는 macOS 빌드에는 엔타이틀먼트가 추가되지 않으므로, 앱을 서명할 때 keychain-access-groups 엔타이틀먼트를 포함했는지 확인하세요.
Windows
- 같은 Windows 사용자 계정으로 실행한 프로그램: 저장한 값을 복호화할 수 있으므로, 여러 사람이 한 Windows 계정을 함께 쓰는 환경을 지원한다면 앱을 종료하거나 로그아웃할 때 인증 정보를 삭제하세요.
- 여러 인스턴스의 동시 접근: 같은 앱을 여러 개 실행해 동시에 저장소에 접근하면 한쪽 호출이
AccessDenied를 반환할 수 있습니다. - 값 덮어쓰기 실패: 같은 키에 저장한 값을 새 값으로 바꾸지 못하면 기존 값은 그대로 남고, 호출은
AccessDenied또는Code가Internal인Failure를 반환합니다. - 실행 선행 조건: 앱을 실행하는 모든 PC에 Microsoft Visual C++ 2015-2022 재배포 가능 패키지(x64)가 설치되어 있어야 하며, 자세한 내용은 Windows 실행 선행 조건을 참조하세요.
메서드 요약
모든 메서드는 마지막 파라미터로 CancellationToken cancellationToken = default를 받습니다. 호출 규약은 호출 컨텍스트를 참조하세요.
- SaveAsync(): 키에 값 저장, 같은 키에 저장한 값이 있으면 덮어씀
- LoadAsync(): 키에 저장한 값 조회
- DeleteAsync(): 키에 저장한 값 삭제
- ClearAsync(): 이 앱이 저장한 모든 값을 삭제해 저장소 비우기
CancellationToken으로 호출을 취소하면 Code가 Cancelled인 Failure를 반환합니다. 다만 이미 시작된 저장소 작업은 중단되지 않으므로, 취소한 저장이나 삭제가 실제로는 완료될 수 있습니다.
공통 파라미터
SaveAsync(), LoadAsync(), DeleteAsync()는 요청을 request 파라미터로 받으며, request는 Required입니다. 아래 메서드 설명에서는 요청 타입만 표기하고 파라미터 표는 생략합니다. 각 요청 타입의 필드는 데이터 타입에서 확인하세요.
발생 예외
ArgumentNullException:request가null인 경우
결과 처리
DataCorrupted와 AccessDenied는 저장된 데이터의 상태가 다르므로 반드시 구분해 처리하세요. 저장소를 비워도 되는 결과는 DataCorrupted뿐입니다.
| 결과 케이스 | 저장된 데이터 | 앱의 처리 |
|---|---|---|
DataCorrupted | 암호화 키나 저장 데이터가 손상되어 저장한 값을 다시 읽을 수 없습니다. | ClearAsync()로 저장소를 비운 뒤 값을 다시 받습니다. 인증 정보였다면 앱 사용자가 다시 로그인하게 합니다. |
AccessDenied | OS가 저장소 접근을 거부했거나 Windows에서 다른 인스턴스가 저장소를 사용 중일 뿐, 저장한 값은 남아 있을 수 있습니다. | 저장소를 비우지 말고 나중에 다시 시도하거나, 저장한 값 없이 진행합니다. |
Failure | 손상을 확정하지 못한 실패입니다. 저장한 값은 남아 있을 수 있습니다. | 저장소를 비우지 말고 Problem으로 원인을 확인합니다. |
UnknownOutcome | 이 SDK 버전이 알지 못하는 결과입니다. | 저장소를 비우지 말고 실패로 처리합니다. |
AccessDenied, Failure, UnknownOutcome에서 저장소를 비우면 아직 유효한 인증 정보까지 사라집니다. 인증 정보를 보존하는 기준은 저장된 인증 정보 보존을 참조하세요.
DataCorrupted는 iOS, macOS, Windows에서는 LoadAsync()에서만 반환됩니다. Android에서는 SaveAsync()와 DeleteAsync()에서도 반환될 수 있습니다.
Failure의 원인은 Problem.Code로 구분합니다.
InvalidArgument: 빈 문자열이거나 256자를 넘는 키FailedPrecondition: iOS, macOS에서 앱에 Keychain 엔타이틀먼트가 없거나 프로비저닝 프로파일이 맞지 않는 경우Cancelled:CancellationToken으로 취소한 호출Internal: 디스크 공간 부족이나 일시적인 입출력 오류로 인한 저장소 작업 실패, 또는 SDK가 네이티브 응답을 읽지 못한 경우
메서드
SaveAsync
키에 값을 암호화해 저장합니다. 같은 키에 저장한 값이 있으면 새 값으로 덮어씁니다. 빈 문자열도 값으로 저장되며, 조회하면 빈 문자열이 반환됩니다.
결과 케이스 — SecureStorageSaveResult
| 결과 케이스 | 와이어 코드 | 설명 |
|---|---|---|
Success | — | 값을 저장했습니다. |
DataCorrupted | data_corrupted | 저장소의 암호화 키나 저장 데이터가 손상되어 값을 저장하지 못했습니다. Android에서만 반환됩니다. |
AccessDenied | access_denied | OS가 저장소 접근을 거부했습니다. 저장한 값은 남아 있을 수 있습니다. |
UnknownOutcome | UNKNOWN | 이 SDK 버전이 알지 못하는 새 결과입니다. |
Failure | FAILURE | 호출을 마치지 못했습니다. Problem의 HiveError로 원인을 확인합니다. |
LoadAsync
키에 저장한 값을 복호화해 반환합니다. 키에 저장한 값이 없으면 Success를 반환하며, 이때 Data.Value는 null입니다. 빈 문자열을 저장한 키는 빈 문자열을 반환하므로, 값이 없는 경우와 구분하려면 null인지 확인하세요.
결과 케이스 — SecureStorageLoadResult
| 결과 케이스 | 와이어 코드 | 설명 |
|---|---|---|
Success | — | 조회에 성공했습니다. 값은 Data.Value에 담기며, 키에 저장한 값이 없으면 null입니다. |
DataCorrupted | data_corrupted | 저장한 값을 복호화할 수 없습니다. |
AccessDenied | access_denied | OS가 저장소 접근을 거부했습니다. 저장한 값은 남아 있을 수 있습니다. |
UnknownOutcome | UNKNOWN | 이 SDK 버전이 알지 못하는 새 결과입니다. |
Failure | FAILURE | 호출을 마치지 못했습니다. Problem의 HiveError로 원인을 확인합니다. |
호출 예시
using Hive.Axyl.Storage;
var result = await storage.LoadAsync(new SecureStorageLoadRequest
{
Key = "refresh_token",
});
switch (result)
{
case SecureStorageLoadResult.Success success:
string? refreshToken = success.Data.Value; // 저장한 값이 없으면 null
break;
case SecureStorageLoadResult.DataCorrupted:
// 저장한 값을 다시 읽을 수 없습니다. 저장소를 비운 뒤 다시 로그인하게 합니다.
await storage.ClearAsync();
break;
default:
// AccessDenied, Failure, UnknownOutcome: 저장소를 비우지 않고 저장한 값 없이 진행합니다.
break;
}
DeleteAsync
키에 저장한 값을 삭제합니다. 키에 저장한 값이 없어도 Success를 반환합니다.
로그아웃할 때는 저장소를 비우지 말고 삭제할 키만 이 메서드로 삭제하세요. ClearAsync()는 로그아웃 뒤에도 유지해야 하는 값까지 삭제합니다.
결과 케이스 — SecureStorageDeleteResult
| 결과 케이스 | 와이어 코드 | 설명 |
|---|---|---|
Success | — | 값을 삭제했거나, 키에 저장한 값이 없습니다. |
DataCorrupted | data_corrupted | 저장 데이터가 손상되어 값을 삭제하지 못했습니다. Android에서만 반환됩니다. |
AccessDenied | access_denied | OS가 저장소 접근을 거부했습니다. 저장한 값은 남아 있을 수 있습니다. |
UnknownOutcome | UNKNOWN | 이 SDK 버전이 알지 못하는 새 결과입니다. |
Failure | FAILURE | 호출을 마치지 못했습니다. Problem의 HiveError로 원인을 확인합니다. |
ClearAsync
이 앱이 ISecureStorage로 저장한 값을 모두 삭제해 저장소를 비웁니다. 저장한 값을 복호화하지 않고 삭제하므로 데이터가 손상된 저장소도 비울 수 있으며, 결과로 DataCorrupted를 반환하지 않습니다. Android에서는 저장소의 암호화 키도 함께 삭제하고, 다음에 값을 저장할 때 새 키를 만듭니다.
이 메서드는 DataCorrupted를 받았을 때 저장소를 복구하는 용도로 사용하세요. 다른 결과에서 호출하면 아직 유효한 값까지 삭제됩니다.
- 요청: 없음
- 응답: SecureStorageClearResponse
결과 케이스 — SecureStorageClearResult
| 결과 케이스 | 와이어 코드 | 설명 |
|---|---|---|
Success | — | 저장소를 비웠습니다. |
AccessDenied | access_denied | 저장소의 값을 삭제하지 못했습니다. 저장한 값의 일부나 전부가 남아 있을 수 있습니다. |
UnknownOutcome | UNKNOWN | 이 SDK 버전이 알지 못하는 새 결과입니다. |
Failure | FAILURE | 호출을 마치지 못했습니다. Problem의 HiveError로 원인을 확인합니다. |
마커 인터페이스
네 메서드의 결과를 결과 타입과 관계없이 한 번에 분기할 때 사용합니다. 두 인터페이스의 네임스페이스는 Hive.Axyl.Storage입니다. 각 결과 타입의 DataCorrupted는 IDataCorruptedOutcome을, AccessDenied는 IAccessDeniedOutcome을 구현합니다. 모든 모듈에 공통인 마커 인터페이스는 Core 결과 모델을 참조하세요.
IDataCorruptedOutcome
interface
저장한 값을 다시 읽을 수 없어 저장소를 비우고 값을 다시 받아야 하는 결과를 표시합니다.
| 프로퍼티 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Code | string | Required | 와이어 코드인 data_corrupted입니다. |
IAccessDeniedOutcome
interface
OS가 저장소 접근을 거부해 저장한 값이 남아 있을 수 있는 결과를 표시합니다.
| 프로퍼티 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Code | string | Required | 와이어 코드인 access_denied입니다. |
데이터 타입
여러 요청 타입이 공유하는 필드는 의미가 같습니다.
Key: 값을 구분하는 키. 앱이 정한 1자 이상 256자 이하의 문자열
키가 빈 문자열이거나 256자를 넘으면 Code가 InvalidArgument인 Failure를 반환합니다. Windows에서는 키의 길이를 UTF-8 바이트 수로 세므로, 모든 플랫폼에서 같은 키를 쓰려면 256자 이하의 ASCII 문자로 키를 만드세요.
SecureStorageClearResponse
필드가 없습니다.
SecureStorageDeleteRequest
값 삭제 요청입니다.
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Key | string | Required | 삭제할 값의 키입니다. |
SecureStorageDeleteResponse
필드가 없습니다.
SecureStorageLoadRequest
값 조회 요청입니다.
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Key | string | Required | 조회할 값의 키입니다. |
SecureStorageLoadResponse
값 조회 결과입니다.
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Value | string? | Optional | 키에 저장한 값입니다. 키에 저장한 값이 없으면 null입니다. 민감 정보이므로 로그에 기록하지 마세요. |
SecureStorageSaveRequest
값 저장 요청입니다.
| 필드 | 타입 | 필수 여부 | 설명 |
|---|---|---|---|
Key | string | Required | 값을 저장할 키입니다. 같은 키에 저장한 값이 있으면 덮어씁니다. |
Value | string | Required | 저장할 값입니다. 빈 문자열도 저장할 수 있습니다. 인증 토큰처럼 작은 값을 저장하는 용도이며, 큰 데이터를 저장했을 때의 동작은 보장하지 않습니다. |
SecureStorageSaveResponse
필드가 없습니다.