Unity + PLATEAU + Geospatial APIでAR作成時のiOSトークン認証について
質問内容
Unity + PLATEAU + Geospatial APIを使ったAR作成におけるiOSトークン認証について以下の状況に陥っており、解決策をご教授いただきたい。
iOSトークン認証とは、具体的には、Unityでこの図のように設定した場合のことです。
経緯
こちらのサイトのおかげで、
こんな感じの建物のオクルージョンに対応したARアプリを作れるようになりました。
広島駅南口のビルの壁面に巨大なスクリーンができたんだ。知らなかったw#plateau pic.twitter.com/1jqwYxLrh4
— たつや (@tatsuya1970) August 19, 2023
これの認証は、設定が簡単なARCoreのAPIキーを使いましたが、
先ほどのPLATEAUの公式サイトによれば
認証方式がいくつかあり、Androidはキーレス認証、iOSはトークン認証を使うのが望ましいですが、手順が複雑であることと、トークン認証ではサーバーが必要であることから、以下では、設定が簡単なAPIキー認証方式を用います。ただし、アプリ内にAPIキーを置くのはセキュリティ的に大きなリスクとなるので、一般に公開するアプリでは、この方式は使わないでください。
ということなので、
このたび一般公開するアプリを作成するために、トークン認証に挑戦しています。
状況
後述するとおり、ARCoreの公式チュートリアルを見ながら
・Googleサービスアカウントと署名鍵を作成
・署名鍵に基づいて、AWS LambdaでJWTのトークンを発行
・UnityからLambdaをたたいて入手したトークンをARcoreに渡す
という手順で進めましたが、最後UnityとXcodeのビルド&ランで、エラーなくiPhone実機も動くのですが、VPSに接続できません。
iPhoneのスクリーンショット
Xcodeのコンソールの表示には、このようなDebugLogのメッセージがあり、トークン認証が反映されてないです。
(GeospatialController.cs の DebugLog)
Geospatial sample encountered an EarthState error: ErrorNotAuthorized
Google.XR.ARCoreExtensions.Samples.Geospatial.GeospatialController:Update()
このDebugLogのメッセージは、
GeospatialController.cs の614行目
「Debug.LogWarning(errorMessage);」
にあたります。
// Check earth state.
var earthState = EarthManager.EarthState;
if (earthState == EarthState.ErrorEarthNotReady)
{
SnackBarText.text = _localizationInitializingMessage;
return;
}
else if (earthState != EarthState.Enabled)
{
string errorMessage =
"Geospatial sample encountered an EarthState error: " + earthState;
Debug.LogWarning(errorMessage);
SnackBarText.text = errorMessage;
return;
}
環境
- macOS Ventura 13.3.1
- Unity 2021.3.4f1
- ARcore Extensions 1.38.0
- Python 3.8(AWS Lambda)
私がやった手順
Googleサービスアカウントの秘密鍵作成
-
Google Cloud Platform Console のナビゲーション メニュー で、目的のプロジェクトを選択し、 APIとサービス > 認証情報 に移動
-
サービスアカウントの詳細で、「サービスアカウント名」を入力。サービスアカウントIDは自動に生成される。 「作成して続行」をクリック
-
「現在使用中」 > 「サービス アカウント トークン作成者」を選択して、「続行」 をクリック。
これで、秘密鍵を含む JSON ファイルがPCにダウンロードされる。
AWS Secret Manager
AWS Secrets Manager
>シークレット
シークレットのタイプを「その他のシークレットのタイプ」を選択
キー/ 値のペア を「プレーンテキスト」を選び、
先ほど生成したJSONファイルの内容をペーストする。
AWS Lambda
サーバーでトークンを作成する必要があります。
私はAWSのLambdaを使いました。
コード
import jwt
import cryptography
import json
import boto3
from datetime import datetime, timedelta
def get_secret(secret_name):
client = boto3.client(YOUR_SECRET_NAME)
response = client.get_secret_value(SecretId=secret_name)
if 'SecretString' in response:
secret = response['SecretString']
return json.loads(secret)
else:
raise ValueError("Failed to retrieve the secret.")
def lambda_handler(event, context):
# AWS Secrets Managerから秘密鍵の情報を取得
# TODO: 'YOUR_SECRET_NAME' を適切なSecrets Managerの秘密鍵名に置き換える
service_account_info = get_secret('plateauAR-key')
private_key = service_account_info['private_key']
client_email = service_account_info['client_email']
current_time = datetime.now() + timedelta(hours=9) #日本時間にする
expiration_time = current_time + timedelta(hours=1)
# print (current_time)
# print (expiration_time)
payload = {
'iss': client_email,
'sub': client_email,
'iat': int(current_time.timestamp()),
'exp': int(expiration_time.timestamp()),
'aud': 'https://arcore.googleapis.com/',
}
token = jwt.encode(payload, private_key, algorithm='RS256')
# print (token)
return {'statusCode': 200, 'body': token}
Pythonライブラリの追加(Lambdaレイヤーの追加)
PyJWT 、 cryptography のライブラリの追加が必要。
ライブラリの追加はレイヤーの追加で対処します。
親切な人が一覧にしてたので、ここでライブラリのARMを探します。
https://github.com/keithrozario/Klayers/tree/master/deployments
参考サイト
https://qiita.com/rapirapi/items/faf18994fcc69a1136bf
ARNがない場合は
python というフォルダを作る
※必ずフォルダ名はPythonで
pip install XXX -t ./python/
または
python3 -m pip install jwt -t ./python/
pythonフォルダを zip化し、
Lambda > レイヤーの追加
レイヤーを選択 > カスタムレイヤー
AWS IAMポリシー設定
JSONを選択し、ポリシーエディタに以下のJSONを入力する。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "シークレットマネージャーのARNを入力"
}
]
}
さきほど作成したポリシーをアタッチ!
Unity
Edit > Project Settings
XR Plugin Management > ARcore Extensions
iOS Authentication Strategy で "Authentication Token" を選択
空のゲームオブジェクト「SetToken」を作り、それに以下のコードをアタッチ。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using Google.XR.ARCoreExtensions;
using UnityEngine.XR.ARFoundation;
using UnityEngine.XR.ARSubsystems;
public class SetToken : MonoBehaviour
{
public ARAnchorManager AnchorManager;
// Start is called before the first frame update
void Start()
{
//TOKEN取得
StartCoroutine(FetchTokenAndSet());
}
IEnumerator FetchTokenAndSet()
{
string endpointURL = "先ほどのAWS LambdaのURL";
UnityWebRequest request = UnityWebRequest.Get(endpointURL);
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Failed to fetch the token: " + request.error);
yield break;
}
string jwt = request.downloadHandler.text;
//デバッグ用にプリント(通常はプリントしないほうがいい)
print("Token :" + jwt);
AnchorManager.SetAuthToken(jwt);
}
}
Inspectorの Anchor Manager は 「AR Session Origin」を選択
これで、ビルド&ランすると、
Unityのビルドは成功し、iOSのビルドも成功。
iPhoneの実機が立ち上がるが、
VPSには繋がらない。
Xcodeのログでは、このようなエラー。
Token :eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwbGF0ZWF5YXItYXBwMkBwbGF0ZWF1LWFyLmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoicGxhdGVheWFyLWFwcDJAcGxhdGVhdS1hci5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImlhdCI6MTY5Mzc1MzQ0MSwiZXhwIjoxNjkzNzU3MDQxLCJhdWQiOiJodHRwczovL2FyY29yZS5nb29nbGVhcGlzLmNvbS8ifQ.IpisakX8GsS9j6v_e0DL3UkTE425_T7QuBeufeT5-zppJdpe7a5DsigxTZIdXMR5l_8xm4PSCZpvB70WJYzHGJh_B6AdZGnVUaYc9_DkHJQkcb1mfHW-_mvzZ0RMLn3woNipbHpAVbgUEf54YJhAsnh73zD-88QM2TjUycYMCmtcoDk5P5rmbIAFdq3OJbESWw900VQNrnLfBMz4QQBpT2GvR8fUz-4vJgSbbc11zguY7-8c9YCX_eh94m2xo_MNvu1DW0UdFcCo4dNfV0K4Cq184wRgVby5cjnCAijA3X3I6uyFQtQsFtTFxsjYQXYVziti8EMl0BwE9yIwwjbjmQ
<FetchTokenAndSet>d__3:MoveNext()
UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
Geospatial sample encountered an EarthState error: ErrorNotAuthorized
Google.XR.ARCoreExtensions.Samples.Geospatial.GeospatialController:Update()
エラーメッセージの該当箇所は、
GeospatialController.csの 614行目
// Check earth state.
var earthState = EarthManager.EarthState;
if (earthState == EarthState.ErrorEarthNotReady)
{
SnackBarText.text = _localizationInitializingMessage;
return;
}
else if (earthState != EarthState.Enabled)
{
string errorMessage =
"Geospatial sample encountered an EarthState error: " + earthState;
Debug.LogWarning(errorMessage);
SnackBarText.text = errorMessage;
return;
}
以上です。