콘텐츠로 이동

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

키에 값을 암호화해 저장합니다. 같은 키에 저장한 값이 있으면 새 값으로 덮어씁니다. 빈 문자열도 값으로 저장되며, 조회하면 빈 문자열이 반환됩니다.

Task<SecureStorageSaveResult> SaveAsync(SecureStorageSaveRequest request, CancellationToken cancellationToken = default)

결과 케이스 — SecureStorageSaveResult

결과 케이스 와이어 코드 설명
Success — 값을 저장했습니다.
DataCorrupted data_corrupted 저장소의 암호화 키나 저장 데이터가 손상되어 값을 저장하지 못했습니다. Android에서만 반환됩니다.
AccessDenied access_denied OS가 저장소 접근을 거부했습니다. 저장한 값은 남아 있을 수 있습니다.
UnknownOutcome UNKNOWN 이 SDK 버전이 알지 못하는 새 결과입니다.
Failure FAILURE 호출을 마치지 못했습니다. Problem의 HiveError로 원인을 확인합니다.

LoadAsync

키에 저장한 값을 복호화해 반환합니다. 키에 저장한 값이 없으면 Success를 반환하며, 이때 Data.Value는 null입니다. 빈 문자열을 저장한 키는 빈 문자열을 반환하므로, 값이 없는 경우와 구분하려면 null인지 확인하세요.

Task<SecureStorageLoadResult> LoadAsync(SecureStorageLoadRequest request, CancellationToken cancellationToken = default)

결과 케이스 — 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()는 로그아웃 뒤에도 유지해야 하는 값까지 삭제합니다.

Task<SecureStorageDeleteResult> DeleteAsync(SecureStorageDeleteRequest request, CancellationToken cancellationToken = default)

결과 케이스 — SecureStorageDeleteResult

결과 케이스 와이어 코드 설명
Success — 값을 삭제했거나, 키에 저장한 값이 없습니다.
DataCorrupted data_corrupted 저장 데이터가 손상되어 값을 삭제하지 못했습니다. Android에서만 반환됩니다.
AccessDenied access_denied OS가 저장소 접근을 거부했습니다. 저장한 값은 남아 있을 수 있습니다.
UnknownOutcome UNKNOWN 이 SDK 버전이 알지 못하는 새 결과입니다.
Failure FAILURE 호출을 마치지 못했습니다. Problem의 HiveError로 원인을 확인합니다.

ClearAsync

이 앱이 ISecureStorage로 저장한 값을 모두 삭제해 저장소를 비웁니다. 저장한 값을 복호화하지 않고 삭제하므로 데이터가 손상된 저장소도 비울 수 있으며, 결과로 DataCorrupted를 반환하지 않습니다. Android에서는 저장소의 암호화 키도 함께 삭제하고, 다음에 값을 저장할 때 새 키를 만듭니다.

이 메서드는 DataCorrupted를 받았을 때 저장소를 복구하는 용도로 사용하세요. 다른 결과에서 호출하면 아직 유효한 값까지 삭제됩니다.

Task<SecureStorageClearResult> ClearAsync(CancellationToken cancellationToken = default)

결과 케이스 — 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입니다.
using Hive.Axyl.Storage;

if (result is IDataCorruptedOutcome)
{
    // 저장소를 비운 뒤 값을 다시 받습니다.
}
else if (result is IAccessDeniedOutcome)
{
    // 저장소를 비우지 않고 나중에 다시 시도합니다.
}

데이터 타입

여러 요청 타입이 공유하는 필드는 의미가 같습니다.

  • 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

필드가 없습니다.