LoginSignup
1
1

スタンドアロンApplication RouterでRedis on SAP BTP, Hyperscaler Optionを使う

Last updated at Posted at 2024-05-23

Redis on SAP BTP, Hyperscaler Optionとは

Redis on SAP BTP, Hyperscaler Optionは、アプリケーションのキャッシュやセッション情報を格納するために使用するサービスです。AWS, Azure, GCPといったハイパースケーラーが提供するRedisキャッシュサービスを、BTPのサービスとして利用できるようになっています。開発者はハイパースケーラーの違いを意識する必要はありません。

image.png
出典:What Is Redis, Hyperscaler Option?

※以下、この記事ではRedis on SAP BTP, Hyperscaler Optionを"Redis"と記載します。

スタンドアロンApplication RouterでのRedisの役割

Application Routerについて

Application RouterはBTPのアプリケーションへユーザがアクセスする際の入り口となるサービスで、ユーザの認証やバックエンドサービスなどへのルーティングを行います。
Application Routerには二種類あります。一つはスタンドアロンのApplication Router(自分で作成する)で、もう一つはSAP Build Work Zoneなどに組み込まれたManaged Approuterです。

スタンドアロンApplication Routerを使用するケースとしては、SAP Build Work Zoneを使用しないケース、およびマルチテナントなアプリケーションを開発するケースなどがあります。

Application Routerについて詳しく知りたい場合は、以下のブログが参考になります。

スタンドアロンApplication RouterでのRedisの必要性

私がRedisについて初めて知ったのは、以下のブログがきっかけでした。

それによると、

  • BTPのアプリケーションでランダムに401(認証)エラーが発生する事象が発生していた
  • 調査の結果、Cloud FoundryのEvacuation(Diago Cellのメンテナンスなどのために、アプリケーションインスタンスを別インスタンスに移行させるプロセス)によってApplication Routerのインスタンスが再作成されたときに、保持していたセッション情報などが失われたと考えられた

ブログでは、Application Routerを使用する場合には以下が必要としています。

  • Redisのような状態保持メカニズムを使用することが本番環境では必須。そうしないと、セルがEvacuateされたときにセッションが失われてしまう
  • 本番環境ではApplication Routerインスタンスを少なくとも2つ、理想的には3つにスケールする。これにより、セルが更新のためEvacuateされるときに、他のApplication Router(セル)がその情報を引き継ぎ、状態を失うことがないようにすることができる

本番環境でスタンドアロンApplication Routerを使用するときは、Redisを使うことが必須ということです。

Service to Application RouterシナリオでのCSRFトークンを使用したいとき

私がRedisの検証をしてみようと思った直接のきっかけは、CAPで作ったODataサービスにCSRFトークンチェックを追加したいと思ったことでした。CAP自体にはCSRFトークンを扱う仕組みはなく、自前の実装かApplication Routerを使用した実装が紹介されています。

Application RouterでCSRFトークンチェックを有効化するには、xs-app.jsonに"csrfProtection": trueの設定を追加します。

{
  "routes": [
    {
      "source": "^/odata/(.*)$",
      "target": "/odata/$1",
      "destination": "srv-api",
      "csrfProtection": true
    }
  ]  
}

しかし、単純にこの設定をしただけだと、PostmanからCSRFトークンを設定しなくてもPOSTリクエストが実行できました。

Application Routerに対してリクエストする場合、認証用のトークンはAuthorizationではなく、x-approuter-authorizationに設定する必要があります

image.png

ドキュメントを読んだところ、Service to Application Routerシナリオのシナリオでは、外部(Redis)でセッション管理をしないとCSRFトークンは有効にならないことがわかりました。そこで、Application RouterでRedisを使用してCSRFトークンハンドリングが有効化になることを検証してみようと思いました。

検証内容

  1. Redis使用前の状態を確認
  2. Application RouterでRedisを有効化する
  3. Redisに格納されたセッション情報を確認

検証環境

BTPのトライアル環境を使用しています。
※トライアル環境ではRedisのサービスインスタンスが1つしか作れませんでした

コンポーネントの構成

CAP-Redis.drawio.png

ソースコード

プロジェクトの構成

.
├── app
│   ├── booksui2 // Fiori elements
│   ├── router   // Application Router
│   └── services.cds
├── db
│   ├── data
│   └──data-model.cds
├── srv
│   └── cat-service.cds
└──  package.json

/app/router/xs-app.jsonの設定は以下のようになっています。

{
  "welcomeFile": "nsbooksui2/index.html",
  "routes": [
    ...,
    {
      "source": "^/odata/(.*)$",
      "target": "/odata/$1",
      "destination": "srv-api",
      "csrfProtection": true
    },
    {
      "source": "^(.*)$",
      "target": "$1",
      "service": "html5-apps-repo-rt",
      "authenticationType": "xsuaa"
    }
  ]  
}

検証手順

1. Redis使用前の状態を確認

アプリケーションをデプロイし、PostmanからApplication Routerにリクエストを送ってみます。

  • OAuth 2.0のトークンを取得します
    image.png

  • 取得したトークンをx-approuter-authorizationヘッダに"Bearer <トークン>"の形で設定します
    image.png

  • そのままPostリクエストを送信すると、リクエストは成功します。CSRFトークンチェックがされていないということです
    image.png

2. Application RouterでRedisを有効化する

以下のステップでRedisを有効化します。

2.1. mta.yamlにRedisのリソースを追加

resourcesセクションに以下の設定を追加します。

resources:
- name: cap-redis-redis
  type: org.cloudfoundry.managed-service
  parameters:
    config:
      engine_version: "6.0"
      eviction_policy: noeviction
    service: redis-cache
    service-plan: trial

Redisのサービスインスタンスを作成するには数十分かかりました。

2.2. Application RouterでRedisを使用する設定

mta.yamlでApplication Routerのモジュールに以下の設定を追加します。

- name: cap-redis
  type: approuter.nodejs
  path: app/router
  properties:
    EXT_SESSION_MGT: |
      {
        "instanceName": "cap-redis-redis", //redisのサービスインスタンス名
        "storageType": "redis",
        "sessionSecret": "G7h2xP8RjWmQ9uV3bLfZ1cDnJ4oB5sY6kMtH0aE2wX9vFi8zK7yCq3R",
        "defaultRetryTimeout": 10000,
        "backOffMultiplier": 10
      }
    SVC2AR_STORE_CSRF_IN_EXTERNAL_SESSION: true
  requires:
  ...
  - name: cap-redis-redis

EXT_SESSION_MGT
ユーザセッションをRedisで保管するための設定です。設定内容はドキュメントに記載されています。sessionSecretはセッションクッキーを生成するためのシークレットで、自分で64文字以上の文字列を作成して設定します。

SVC2AR_STORE_CSRF_IN_EXTERNAL_SESSION
Service to Application RouterシナリオでCSRFトークンを使用したい場合、trueを設定します。ブラウザからログインするシナリオの場合、この設定は不要です。

2.3. CSRFトークンチェックが有効化されたことを確認

Postmanからステップ1と同じリクエストを送信すると、403 (Forbidden) エラーになります。CSRFトークンチェックが有効化されたためです。
image.png

POSTリクエストを送るには、まずGet(またはHead)リクエストによりCSRFトークンを取得します。x-csrf-tokenヘッダにfetchを指定することで、レスポンスヘッダにトークンが返ります。
image.png

取得したトークンをPOSTリクエストのx-csrf-tokenヘッダに設定して実行すると、リクエストは成功します。
image.png

3. Redisに格納されたセッション情報を確認

Redisのキャッシュは以下のステップで確認できます。

  1. redis-cliをインストール
  2. Application RouterにSSHで接続
  3. redis-cliでセッション情報を確認
  4. セッション情報の中身を確認

3.1. redis-cliをインストール

linux (Ubuntu) の場合、以下のコマンドでインストールできます。

sudo apt-get install redis-tools
redis-cli --version //インストールされたことを確認

3.2. Application RouterにSSHで接続

ドキュメントを参考に、Application RouterにSSHで接続します。

  • SSHを有効化
cf enable-ssh <app name> //SSHを有効化
cf restart <app name>    //アプリケーションを再起動
  • Reidsインスタンスのサービスキーを作成して表示
cf service-key <instance name> <key name>
  • SSHトンネルを設定(以下ではローカルポート6666を使用)
cf ssh -L 6666:<instance hostname>:<instance port> <host app name>

例)
cf ssh -L 6666:master.rg-00b26854-749a-47eb-a6d1-41f6ecb16c9d.9u04ff.use1.cache.amazonaws.com:1460 cap-redis
vcap@740f7311-ba59-4946-66d6-52cc:~$
  • 新規のターミナルを立ち上げ、Redisのインスタンスに接続
redis-cli --tls -c -p 6666 -a <instance password>
127.0.0.1:6666> 

3.3. redis-cliでセッション情報を確認

Redisに格納されたキーの一覧を確認します。Postmanから接続したセッションが{external-session}として登録されています。

KEYS *
1) "{external-sessions}:91006ab733d64a139d8ecf637c258320"

※UIから接続した場合は、{approuter-sessions}で始まるキーが登録されます。

1) "{approuter-sessions}:ktS8iAaHA3NNjRojQET6gDODc_KEA9nV"

キーに対応する値を取得します。

GET <キー値>

例)
GET {external-sessions}:91006ab733d64a139d8ecf637c258320 
"H4sIAAAAAAAEA61WWZOqSBr9Kz283rKKfamIeUBFTYRUUGSZmriRQCprgiwqdNz/3oFVHTHd/XK7Y3ghkvzW8y2HXyn86HBDUHHAbZtWBMTUO6UwNC2iUOK4WOQRwymxjKOzyEkRK8gcS1Mvf1bTHnXaoC6tCPXOSIzICzwtKS9Udu9sfG5wm・・・

このままでば解読できないので、ChatGPTにこのデータ形式は何か聞いたところ、以下の回答でした。

提供されたデータはGzip圧縮されたバイナリデータのようです。これを読める形式にするためには、Gzip圧縮を解凍する必要があります

4.4. セッション情報の中身を確認

ChatGPTの助けを借りて、セッション情報を解凍するスクリプトを作成しました。

decode_redis_data.sh
#!/bin/bash

# Redisからデータを取得してBase64デコード
compressed_data='3.3で取得した値'
echo $compressed_data | base64 --decode > compressed_data.gz

# Gzip解凍
gzip -d -c compressed_data.gz > decompressed_data.txt

# エスケープされたJSON文字列を読み込み、正しいJSON形式に変換して保存
escaped_json=$(cat decompressed_data.txt)
echo $escaped_json | jq -r . > decompressed_data.json

# 正しいJSON形式のデータを表示
cat decompressed_data.json

# 一時ファイルを削除
rm compressed_data.gz decompressed_data.txt

新規ターミナルを立ち上げ、スクリプトを実行します。

chmod +x decode_redis_data.sh
./decode_redis_data.sh

解凍結果は以下のようになりました。セッション情報としてaccessTokenやxsrf-tokenが保存されていることがわかります。

{
  "externalSessionId": "91006ab733d64a139d8ecf637c258320",
  "externalSessionExpiration": 1716454079,
  "jwtRefreshStarted": true,
  "user": {
    "userId": "n/a",
    "name": "n/a",
    "token": {
      "accessToken": "XXXX",
      "expiryDate": 1718129515764382,
      "oauthOptions": {
        "tenantmode": "dedicated",
        "sburl": "https://internal-xsuaa.authentication.us10.hana.ondemand.com",
        "subaccountid": "75ba9a19-b434-4743-89e6-4a9d9ab3e5e8",
        "credential-type": "instance-secret",
        "clientid": "XXXX",
        "xsappname": "cap-redis-a2c7c84etrial-dev!t266030",
        "clientsecret": "XXXX",
        "serviceInstanceId": "29fb9fcd-376f-4631-ad8f-f250913c757a",
        "url": "https://a2c7c84etrial.authentication.us10.hana.ondemand.com",
        "uaadomain": "authentication.us10.hana.ondemand.com",
        "verificationkey": "-----BEGIN PUBLIC KEY-----\nXXXX\n-----END PUBLIC KEY-----",
        "apiurl": "https://api.authentication.us10.hana.ondemand.com",
        "identityzone": "a2c7c84etrial",
        "identityzoneid": "75ba9a19-b434-4743-89e6-4a9d9ab3e5e8",
        "tenantid": "75ba9a19-b434-4743-89e6-4a9d9ab3e5e8",
        "zoneid": "75ba9a19-b434-4743-89e6-4a9d9ab3e5e8"
      }
    },
    "tenantid": "75ba9a19-b434-4743-89e6-4a9d9ab3e5e8",
    "scopes": [
      "uaa.resource"
    ],
    "tenant": "a2c7c84etrial"
  },
  "xsrf": {
    "token": "05a99e23a2e36bf7-dSPc2omar80L9EWLRQJ4rZQ5eAI",
    "secret": "gUasKun6U8eMapRThKpyIbNWX5HF3h1juEIH8P7ynos"
  }
}

まとめ

  • スタンドアロンApplication Routerを本番環境で使用する場合、Redisを使用する
  • Application RouterでRedisを使用するには、環境変数EXT_SESSION_MGTを設定する
  • Service to Application RouterシナリオでCSRFトークンを使用したい場合は、上記に加えてSVC2AR_STORE_CSRF_IN_EXTERNAL_SESSIONも設定する
1
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
1
1