LoginSignup
0
1

More than 1 year has passed since last update.

.NET CoreのAWS Serverless ApplicationプロジェクトでHttpApiを使う

Last updated at Posted at 2021-08-05

はじめに

Visual StudioでAWS Serverless ApplciationでサーバレスのWebアプリケーションを作成すると、標準ではAPI Gateway REST APIを利用するように構成されます。この記事では最新のAPI Gateway HTTP APIを利用する方法について説明します。

API Gateway HTTP APIAPI Gateway REST APIよりも高速で、低価格、高機能なAPI Gatewayのバージョンです。詳しくは下記のドキュメントを参照してください。

プロジェクトの種類

AWS Toolkit for Visual StudioをインストールするとAWS Serverless Applciation(with Tests)というプロジェクトテンプレートが有効になり、下記のBlueprintからAWSのサーバレスアプリケーションを作成することができます。
image.png
.NET Core 3.1を利用する場合はASP.NET Core Web APIASP.NET Core Web Appのどちらかを選択します。Startup.csの違いだけなのでどちらを選択しても問題ありません。
.NET 5を利用する場合は、現時点ではLambdaが.NET 5をネイティブにサポートしていないためASP.NET Core 5(Container Image)を選択する必要があります。

プログラムのエントリーポイント

プロジェクト作成すると、下図のような構成でプロジェクトが作成されます。
image.png
ASP.NET CoreではProgram.csがエントリーポイントになりますが、AWS Serverless ApplicationのASP.NET Coreプロジェクトでは、LambdaEntryPoint.csLocalEntryPoint.csのどちらかがエントリーポイントになります。。

LocalEntryPoint.csはローカル環境でのデバック実行時に呼びだされるエントリーポイントです。ASP.NET CoreのProgram.csに記載されている内容と変わらないので特に迷うところはないと思います。対してLambdaEntryPoint.csはLambdaのコールドスタート時に呼びだされるエントリーポイントです。

下記はLambdaEntryPoint.csに記載されているコメントの抜粋です。LambdaEntryPoint.csはこのコメントにあるように、Lambdaの前段にあるサービスによって継承するクラスを切り替える必要があります。デフォルトではAPI GatewayがAPI Gateway REST APIとして構成されるため、Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunctionが継承されています。

// The base class must be set to match the AWS service invoking the Lambda function. If not Amazon.Lambda.AspNetCoreServer
// will fail to convert the incoming request correctly into a valid ASP.NET Core request.
//
// API Gateway REST API                         -> Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
// API Gateway HTTP API payload version 1.0     -> Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
// API Gateway HTTP API payload version 2.0     -> Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction
// Application Load Balancer                    -> Amazon.Lambda.AspNetCoreServer.ApplicationLoadBalancerFunction
// 
// Note: When using the AWS::Serverless::Function resource with an event type of "HttpApi" then payload version 2.0
// will be the default and you must make Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction the 

API Gateway HTTP APIを使うように構成する

API GatewayにAPI Gateway HTTP APIを利用する場合は下記の2つの手順を実施します。

  • LambdaEntryPointの継承元クラスをAPIGatewayHttpApiV2ProxyFunctionに変更する
  • serverless.templateのResourceタイプをApiからHttpApiに変更する

まず、継承元クラスをAmazon.Lambda.AspNetCoreServer.APIGatewayProxyFunctionからAmazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunctionに変更します。

LambdaEntryPoint.cs
namespace AWSServerless8
{
    public class LambdaEntryPoint :
        // Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
        Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction
    {
        // ...略...
    }
}

続いて、serverless.templateのResourceタイプをApiからHttpApiに変更します。また、${ServerlessRestApi}変数を${ServerlessHttpApi}に変更します。

serverless.template
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Transform": "AWS::Serverless-2016-10-31",
  "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.",
  "Parameters": {},
  "Conditions": {},
  "Resources": {
    "AspNetCoreFunction": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "Handler": "AWSServerless8::AWSServerless8.LambdaEntryPoint::FunctionHandlerAsync",
        "Runtime": "dotnetcore3.1",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": null,
        "Policies": [
          "AWSLambda_FullAccess"
        ],
        "Events": {
          "ProxyResource": {
-           "Type": "Api",
+           "Type": "HttpApi",
            "Properties": {
              "Path": "/{proxy+}",
              "Method": "ANY"
            }
          },
          "RootResource": {
-           "Type": "Api",
+           "Type": "HttpApi",
            "Properties": {
              "Path": "/",
              "Method": "ANY"
            }
          }
        }
      }
    }
  },
  "Outputs": {
    "ApiURL": {
      "Description": "API endpoint URL for Prod environment",
      "Value": {
-       "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
+       "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
      }
    }
  }
}

デプロイすると、API Gatewayの設定画面でプロトコルがRESTからHTTPになっているのが確認できます。

変更前
image.png
変更後
image.png

APIを呼び出すとちゃんと結果が返ってきますね。

$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/values
["value1","value2"]

API GatewayにOIDCの認証を委譲する

ASP.NET Coreでサーバレスアプリケーションを組み込む場合は、ASP.NET Core側でやってもそんなに手間ではないですが、せっかくなのでHTTP APIの機能を使ってOIDCの認証をAPI Gatewayに受け持ってもらいましょう。

IdentityServer 4のデモサーバーを使ってアクセストークンを取得します。

curl -X POST https://demo.identityserver.io/connect/token \
      --data-urlencode "grant_type=client_credentials" \
      --data-urlencode "client_id=m2m.short" \
      --data-urlencode "client_secret=secret" 

このアクセストークンは次のような構成になっているので、API Gatewayではissがhttps://demo.identityserver.ioで、scopeにapi.scope1を持っていることを検証することにしましょう。

{
  "nbf": 1628133438,
  "exp": 1628133513,
  "iss": "https://demo.identityserver.io",
  "aud": "api",
  "client_id": "m2m.short",
  "jti": "1F8BBB5F7E6513B215C15351BF102424",
  "iat": 1628133438,
  "scope": [
    "api",
    "api.scope1",
    "api.scope2",
    "scope2"
  ]
}

ドキュメントを見ながら設定してみましたが、、、
image.png
んー、Visual Studioの拡張機能がAuthorizationScopesを認識してくれないのか、エラーになってしまいます。無理やりデプロイしようとしても、こんなエラーメッセージが表示されデプロイが失敗してしまいました。

Failed to create CloudFormation change set: Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [AspNetCoreFunction] is invalid. Event with id [ProxyResource] is invalid. Unable to set Authorizer [OpenIdAuth] on API method [any] for path [/{proxy+}] because the related API does not define any Authorizers.
Failed to publish AWS Serverless application

仕方がないので、API Gatewayを直接操作していきましょう。
オーソライザーを作成してアタッチをクリックして、
image.png

次の値を設定して作成ボタンをクリックします。

項目 設定値 備考
オーソライザーのタイプ JWT
名前 OpenIdAuth 見出しなので任意の名前を設定
ID ソース $request.header.Authorization
発行者 URL https://demo.identityserver.io issクレームの値
対象者 api audクレームかclient_idクレームの値

認可ページで/{proxy+}にJWT認証というマークがついたのを確認できます。続けて、認可スコープにapi.scope1を設定して保存します。
image.png

この状態でAPIを呼び出すとUnauthorizedで拒否されるようになりました。

$ curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/values
{"message":"Unauthorized"}

改めてIdentityServer 4 のデモサーバーからアクセストークンを発行して

$ curl -X POST https://demo.identityserver.io/connect/token \
       --data-urlencode "grant_type=client_credentials" \
       --data-urlencode "client_id=m2m.short" \
       --data-urlencode "client_secret=secret"
{"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg5QkVCRDE0MkI4M0U4RUEyNjQ3Q0U2MkNGRTQxMENFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MjgxMzg0MzUsImV4cCI6MTYyODEzODUxMCwiaXNzIjoiaHR0cHM6Ly9kZW1vLmlkZW50aXR5c2VydmVyLmlvIiwiYXVkIjoiYXBpIiwiY2xpZW50X2lkIjoibTJtLnNob3J0IiwianRpIjoiRTkzRjBCN0ZDNUIwRTRCOTc2OTQ5N0E1NUQ5QkYyRDQiLCJpYXQiOjE2MjgxMzg0MzUsInNjb3BlIjpbImFwaSIsImFwaS5zY29wZTEiLCJhcGkuc2NvcGUyIiwic2NvcGUyIl19.R7ki8Ww57wRhO_z39VlLPfAJmXfAL6BAvYEIPsmj82QukCNk7jC67Ge-NvUOoYx7XNWbWyD6WQodghXxlHUSvZRmwyqBP15v6UPWd3lRPhNiof9PyD-jvzyp51248DFBmcCJ1KFkvOZxeZTDe3M94wMEk-OHIytem9m_1AYLGLqLpmRq4T0CXkzu77yGgQ9TX0jroYBjdJIFpX86JDGc7vI7RS-HvL_5witlYC3KceUDhR-Uv-IImDozChufS0pln7ZUuAFIcKX0BwNeneiaYQBF_a_DOmjqSgsKscczZ8rmvujJv4We1ikOWXuKn2lR0UOUnLcehqszjI7O5AK8MA","expires_in":75,"token_type":"Bearer","scope":"api api.scope1 api.scope2 scope2"}

返ってきたアクセストークンをauthorizationヘッダーに乗せてAPIを実行するとちゃんと結果が返ってきましたね。

$ curl https://7bp7g5aqwa.execute-api.ap-northeast-1.amazonaws.com/api/values \
     --header 'authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg5QkVCRDE0MkI4M0U4RUEyNjQ3Q0U2MkNGRTQxMENFIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2MjgxMzg0MzUsImV4cCI6MTYyODEzODUxMCwiaXNzIjoiaHR0cHM6Ly9kZW1vLmlkZW50aXR5c2VydmVyLmlvIiwiYXVkIjoiYXBpIiwiY2xpZW50X2lkIjoibTJtLnNob3J0IiwianRpIjoiRTkzRjBCN0ZDNUIwRTRCOTc2OTQ5N0E1NUQ5QkYyRDQiLCJpYXQiOjE2MjgxMzg0MzUsInNjb3BlIjpbImFwaSIsImFwaS5zY29wZTEiLCJhcGkuc2NvcGUyIiwic2NvcGUyIl19.R7ki8Ww57wRhO_z39VlLPfAJmXfAL6BAvYEIPsmj82QukCNk7jC67Ge-NvUOoYx7XNWbWyD6WQodghXxlHUSvZRmwyqBP15v6UPWd3lRPhNiof9PyD-jvzyp51248DFBmcCJ1KFkvOZxeZTDe3M94wMEk-OHIytem9m_1AYLGLqLpmRq4T0CXkzu77yGgQ9TX0jroYBjdJIFpX86JDGc7vI7RS-HvL_5witlYC3KceUDhR-Uv-IImDozChufS0pln7ZUuAFIcKX0BwNeneiaYQBF_a_DOmjqSgsKscczZ8rmvujJv4We1ikOWXuKn2lR0UOUnLcehqszjI7O5AK8MA'
["value1","value2"]

まとめ

  • AWS Serverless ApplciationでAPI GatewayにHTTP APIを使いたい場合は、エントリーポイントの継承元をAmazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunctionに変更して、serverless.templateのResourceタイプをHttpApiを変更すれば利用できる
  • serverless.templateではJWT用のAuthoizerの設定が行えないっぽいので、設定を行う場合はマネジメントコンソールから行う
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1