こんにちは!金融ソリューション事業部の孫です。 Part1の記事 では、FlexMatchに関する コンポーネント の構築をご紹介しました! さて、Part2である今回は、 プレイヤーの認証・管理用Cognitoの作成 UEクライアントの組込みに関するバックエンド API の作成 をご紹介します! Part2の続きとして、Part3の記事は こちら です! プレイヤー管理用 Amazon Cognitoの作成 マッチング検証にあたり、複数プレイヤーの登録が必要になります。 その為、プレイヤーの認証、承認および管理をシンプルにする為、 Amazon Cognitoを採用します。 AWS マネジメントコンソールでCognitoのトップページに移動します 左側Menu「ユーザープール」⇒「ユーザープールの作成」ボタンをクリックして作成ページを開きます Cognitoの設定は、以下の内容を入力します Pool Name: 「GameLiftUnreal-UserPool」とします 「Review Defauls」をクリックし、設定を確認した上で、プールを作成します ※Tips: ここまでの設定はプール作成後、変更できないため、ちゃんと確認してから前に進めてください プールの作成完了後、左Menuに「App Clients」⇒ 「Add an app client」をクリックして以下の内容でAPPクライアントを作成します App client name:「GameLiftUnreal-LambdaLoginClient」としました 「Generate client secret」のチェックを外します 「Auth Flows Configuration」: 「Enable username password based authentication」と「Enable refresh token based authentication」のみチェックします そのほか:デフォルト値 左menu「App Integration」⇒「App Client Settings」をクリックし設定を行います Cognito User Pool: チェック入れます Sign in and sign out URLs -> CallBack URL(s): https://aws.amazon.com Sign in and sign out URLs -> Sign out URL(s): https://aws.amazon.com 左menu「App Integration」⇒「Domain name」のDomain Prefix: 今回は「gameliftunreal-cog-20230222」 としました ここまで、Cognitoの構築は完了しました。 次に、UEクライアントとCognito連携用の API を構築します。 AWS マネジメントコンソールでLambdaのトップページに移動します 「関数の作成」ボタンをクリックして、関数作成ページを開きます 以下の内容で関数を作成します。 関数名:「GameLiftUnreal-CognitoLogin」としました ランタイム:「Python3.9」としました 関数コードは、以下の内容を入力します 関数の役割としては、プレイヤーが入力したユーザー名とパスワードをCognitoに送信し、その認証結果をプレイヤーに返却します GameLiftUnreal-CognitoLoginコード import boto3 import os import sys USER_POOL_APP_CLIENT_ID = 'Cognitoで生成したクライアントアプリへのアクセスキー' client = boto3.client('cognito-idp') def lambda_handler(event, context): if 'username' not in event or 'password' not in event: return { 'status': 'fail', 'msg': 'Username and password are required' } resp, msg = initiate_auth(event['username'], event['password']) if msg != None: return { 'status': 'fail', 'msg': msg } return { 'status': 'success', 'tokens': resp['AuthenticationResult'] } def initiate_auth(username, password): try: resp = client.initiate_auth( ClientId=USER_POOL_APP_CLIENT_ID, AuthFlow='USER_PASSWORD_AUTH', AuthParameters={ 'USERNAME': username, 'PASSWORD': password }) except client.exceptions.InvalidParameterException as e: return None, "Username and password must not be empty" except (client.exceptions.NotAuthorizedException, client.exceptions.UserNotFoundException) as e: return None, "Username or password is incorrect" except Exception as e: print("Uncaught exception:", e, file=sys.stderr) return None, "Unknown error" return resp, None Cognitoへのアクセスが必要なため、アクセス権限を追加します 「設定」⇒「アクセス権限」で、対象ロールをクリックしてアクセス権限編集ページにとばされます JSON タブの配下に、以下の JSON 文を文末に追記します { " Sid ": " VisualEditor0 ", " Effect ": " Allow ", " Action ": " cognito-idp:InitiateAuth ", " Resource ": " * " } ここまでは、GameLiftUnreal-CognitoLogin関数を作成しました。 次に、 API Gateway において、関数を外から呼びだす API を作成します。 AWS マネジメントコンソールの検索窓口に「 API Gateway 」を入力して API Gateway のトップページに移動します RestAPIを選択し、「構築」ボタンを押して API の作成ページを開きます 以下の内容で API を作成します プロトコル を選択する:Rest 新しい API の作成 : New API 名前と説明 API 名 : 「GameLiftUnreal- API 」としました その他:デフォルト値 「 API の作成」ボタンを押して API を作成しました。 作成した API を開いて「アクション」⇒「リソースの作成」でloginリソースを作成します 「アクション」⇒「メソッドの作成」でPOSTメソッドを作成します 統合タイプ:lambda Functionチェック Lambdaリージョン:ap-northeast-1 Lambda 関数:前手順で作成したGameLiftUnreal-CognitoLoginを選択 プレイヤーのIDとパスワードを入力した上で、ログイン API にリク エス トしたら、Cognitoからアクセスキーを発行してくれます。 そして、発行されたアクセスキーを用いて、他の API にも正常にリク エス トができます。 マッチング処理用のバックエンド API の作成 上記AmazonSNS作成時に説明した通り、各マッチングイベントごとに後続処理の実装が必要なのですが、今回は検証をシンプルにする目的で「StartMatchMaking」イベントのみ処理します。 また、その他の補助処理として「①GetPlayerData:プレイヤーデータ取得」と「②PollMatchMaking:マッチング結果取得」も実装します。 バックエンドのLambda関数を作成します 上記のLambda作成と同様な手順でLambda作成ページに移動し、以下の内容で関数本体を作成します StartMatchmaking 関数の役割としては、マッチングで必要なデータをインプットしてマッチングを開始します 関数名:「StartMatchmaking」としました ランタイム:「NodeJs 18.x」としました GetPlayerData 関数の役割としては、現在ログイン中のプレイヤー情報を取得します 関数名:「GetPlayerData」としました ランタイム:「NodeJs 18.x」としました PollMatchMakingのLambda関数定義 関数の役割としては、マッチング結果を取得します(ポーリング方式) 関数名:「PollMatchMaking」としました ランタイム:「NodeJs 18.x」としました 上記作成した関数本体を開いて、対象の ソースコード を入力します StartMatchmakingコード const AWS = require(' aws - sdk '); const Lambda = new AWS .Lambda({region: 'ap-northeast-1'}); const GameLift = new AWS .GameLift({region: 'ap-northeast-1'}); exports.handler = async (event) => { let response; let raisedError; let latencyMap; if (event.body) { const body = JSON .parse(event.body); if (body.latencyMap) { latencyMap = body.latencyMap; } } if (!latencyMap) { response = { statusCode: 400, body: JSON .stringify({ error: 'incoming request did not have a latency map' }) }; return response; } const lambdaRequestParams = { FunctionName: 'GetPlayerData', Payload: JSON .stringify(event) }; let playerData; await Lambda. invoke (lambdaRequestParams) .promise().then(data => { if (data && data.Payload) { const payload = JSON .parse(data.Payload); if (payload.body) { const payloadBody = JSON .parse(payload.body); playerData = payloadBody.playerData; } } }) .catch(err => { raisedError = err; }); if (raisedError) { response = { statusCode: 400, body: JSON .stringify({ error: raisedError }) }; return response; } else if (!playerData) { response = { statusCode: 400, body: JSON .stringify({ error: 'unable to retrieve player data' }) }; return response; } const playerId = playerData.Id.S; const groupId = parseInt(playerData.groupId.N, 10); const gameLiftRequestParams = { ConfigurationName: 'GameLiftTutorialMatchmaker', Players: [{ LatencyInMs: latencyMap, PlayerId: playerId, PlayerAttributes: { groupid: { N: groupId } } }] }; console.log('matchmaking request: ' + JSON .stringify(gameLiftRequestParams)); let ticketId; await GameLift.startMatchmaking(gameLiftRequestParams) .promise().then(data => { if (data && data.MatchmakingTicket) { ticketId = data.MatchmakingTicket.TicketId; } response = { statusCode: 200, body: JSON .stringify({ 'ticketId': ticketId }) }; }) .catch(err => { response = { statusCode: 400, body: JSON .stringify({ error: err }) }; }); return response; }; GetPlayerDataコード const AWS = require(' aws - sdk '); const Cognito = new AWS .CognitoIdentityServiceProvider({region: 'ap-northeast-1'}); const DynamoDb = new AWS .DynamoDB({region: 'ap-northeast-11'}); exports.handler = async (event) => { let response; let raisedError; let accessToken; if (event.headers) { if (event.headers['Authorization']) { accessToken = event.headers['Authorization']; } } const cognitoRequestParams = { AccessToken: accessToken }; let sub; await Cognito.getUser(cognitoRequestParams) .promise().then(data => { if (data && data.UserAttributes) { for (const attribute of data.UserAttributes) { if (attribute.Name == 'sub') { sub = attribute. Value ; break; } } } }) .catch(err => { raisedError = err; }); if (raisedError) { response = { statusCode: 400, body: JSON .stringify({ error: raisedError }) }; return response; } const dynamoDbRequestParams = { TableName: 'Players', Key: { Id: {S: sub} } }; let playerData; await DynamoDb.getItem(dynamoDbRequestParams) .promise().then(data => { if (data && data.Item) { playerData = data.Item; } response = { statusCode: 200, body: JSON .stringify({ 'playerData': playerData }) }; }) .catch(err => { response = { statusCode: 400, body: JSON .stringify({ error: err }) }; }); return response; }; PollMatchMakingコード const AWS = require(' aws - sdk '); const DynamoDb = new AWS .DynamoDB({region: 'ap-northeast-1'}); exports.handler = async (event) => { let response; let ticketId; if (event.body) { const body = JSON .parse(event.body); if (body.ticketId) { ticketId = body.ticketId; } } if (!ticketId) { response = { statusCode: 400, body: JSON .stringify({ error: 'incoming request did not have a ticket id' }) }; return response; } const dynamoDbRequestParams = { TableName: 'MatchmakingTickets', Key: { Id: {S: ticketId} } }; let ticket; await DynamoDb.getItem(dynamoDbRequestParams) .promise().then(data => { if (data && data.Item) { ticket = data.Item; } response = { statusCode: 200, body: JSON .stringify({ 'ticket': ticket }) }; }) .catch(err => { response = { statusCode: 400, body: JSON .stringify({ error: err }) }; }); return response; }; 各関数のアクセス権限を編集します 1.StartMatchmakingの アクセスポリシーに以下の内容を文末に追加します { " Effect ": " Allow ", " Action ": " lambda:InvokeFunction ", " Resource ": " arn:aws:lambda:your_region:your_account:function:GetPlayerData " , } , { " Effect ": " Allow ", " Action ": " gamelift:StartMatchmaking ", " Resource ": " * " , } 2.GetPlayerDataの アクセスポリシーに以下の内容を文末に追加します { " Effect ": " Allow ", " Action ": " Dynamodb:GetItem ", " Resource ": " arn:aws:dynamodb:your_region:your_account:table/Players " , } 3.PollMatchMakingの アクセスポリシーに以下の内容を文末に追加します { " Effect ": " Allow ", " Action ": " Dynamodb:GetItem ", " Resource ": " arn:aws:dynamodb:your_region:your_account:table/MatchmakingTickets " , } API Gateway での API の作成 上記ログイン API の作成と同様な手順で API の作成ページに移動し、以下の内容で API リソースを作成します getplayerdata API リソースの作成 Resource Name:getplayerdata Resource Path : /getplayerdata その他:デフォルト値 startmatchmaking API リソースの作成 Resource Name:startmatchmaking Resource Path:/startmatchmaking pollmatchmaking API リソースの作成 Resource Name:pollmatchmaking Resource Path:/pollMatchmaking API ごとにメソッドを作成します getplayerdata API のGETメソッド作成 統合タイプ:lambda Functionチェック Lambdaリージョン:ap-northeast-1 Lambda 関数:GetPlayerData startmatchmaking API のPOSTメソッド作成 統合タイプ:lambda Functionチェック Lambdaリージョン:ap-northeast-1 Lambda 関数:StartMatchMaking pollmatchmaking API のPOSTメソッド作成 統合タイプ:lambda Functionチェック Lambdaリージョン:ap-northeast-1 Lambda 関数:PollMatchMaking API ごとに認証を設定します 図のように各 API メソッドごとに「メソッドリク エス ト」⇒「認可」にCognito認証を設定します ここまで、Cognitoおよびバックエンド API の作成は、以上となります! Part3 では、バックエンド API を用いてUEクライアントへの組込みおよびマッチングの検証を行います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )