7
2

More than 3 years have passed since last update.

【CAP】XSUAAによる認証を追加・ローカルでテスト

Last updated at Posted at 2020-08-25

はじめに

以前の記事で、CAPのHello World的なサービスを作成しました。
このサービスは"誰でも"使える状態でしたが、アクセスできるユーザを絞るようにしたいです。
【CAP】CAPで作ったODataをUI5アプリで使う(1) CAPサービスの作成

そこで、この記事では以下のテーマについて扱います。
・CAPで作ったODataサービスをCloud Platormで認証されたユーザのみ使用可能にする
・認証の設定をしたうえでローカルでも実行できるようにする

Cloud Platformでのユーザ認証の仕組み

Cloud Platformで動くアプリケーションでは、ApprouterとXSUAAサービスのコンビネーションにより認証が行われます。以下のページの説明がわかりやすかったので翻訳・引用します。
https://github.com/SAP/cloud-security-xsuaa-integration

image.png
1. ユーザがWebアプリケーションにブラウザやモバイルデバイスを介してアクセスする
2. Webアプリケーション(典型的なSAP Cloud Platformアプリケーションの場合、Application Router)はOAuthクライアントとして、認証のためにOAuthサーバにリダイレクトする ⇒ユーザはID、パスワードの入力を求められる
3. 認証されるとWebアプリケーションはOAuthサーバから発行されたコードを使ってアクセストークンを要求する
4. Webアプリケーションはアクセストークンを使ってOAuthリソースサーバからデータを取得する。OAuthリソースサーバはオンラインまたはオフラインでトークンを検証する

アクセストークンはJWT(Json Web Token)とも呼ばれます。ユーザID、氏名などのほか、ユーザが持つ権限(スコープ)が記載されています。

CAPプロジェクトに認証を追加

以下の記事で作成したCAPプロジェクトに認証を追加します。
【CAP】CAPで作ったODataをUI5アプリで使う(1) CAPサービスの作成

<ステップ>
1. アクセスコントロール用のアノテーションを追加
2. モックの認証を設定(ローカル実行用)
3. トークンを使用した認証を設定(ローカル、本番実行用)

1. アクセスコントロール用のアノテーションを追加

もともとあったproduct-service.cdsに加え、admin-service.cdsを作成します。
image.png
Productサービスは認証されたユーザのみ実行可能、Adminサービスは"admin"ロールを持つユーザのみ実行可能とします。

アクセスコントロールの設定方法については以下のドキュメントをご参照ください。
https://cap.cloud.sap/docs/guides/authorization#roles

product-service.cds
using demo.products as prod from '../db/schema';

@requires: 'authenticated-user'
service ProductService {
    entity Products as projection on prod.Products;
}
admin-service.cds
using demo.products as prod from '../db/schema';

@requires: 'admin'
service AdminService {
    entity Products as projection on prod.Products;
}

cds watchで実行してみます。まずProductサービスのProductsエンティティを見てみます。
image.png
認証を求められますが、何も入力せずにサインインしてもデータが表示されます。
image.png
次に、AdminサービスのProductsエンティティを見てみます。こちらは権限がないのでエラーになりました。
image.png

<ここまででわかったこと>
ローカル実行の場合、
・authenticated-userが実行できるサービスは、サインインした後は誰でも使える
・特定のロールのみ実行可能なサービスは、そのロールを持つユーザでないと使えない

2. モックの認証を設定

ローカルでもロールを持ったユーザとして実行できるように、モックの認証の設定をします。
まず、passportをプロジェクトに追加します。

npm add passport

続いて、.cdsrc.jsonファイルを以下のように設定します。これで、adminロールを持つ"admin"というユーザを作成したことになります。(.cdsrc.jsonはプロジェクトのルートに置きます)

.cdsrc.json
{
    "auth": {
        "passport": {
            "strategy": "mock", 
            "users": {
                "admin": { 
                    "password": "dummy", 
                    "ID": "admin",
                    "roles": ["admin", "authenticated-user"]
                }                        
            }
        }
    }    
}

再度実行してみましょう。前回のログイン情報が影響しないようにシークレットウインドウで開き、adminユーザでログインします。
image.png
AdminサービスのProductsエンティティが表示できました。
image.png

3. トークンを使用した認証を設定

CAPプロジェクトにXSUAAの設定を追加します。最初に本番用の設定をしてCloud Foundryにデプロイしてから、ローカル実行用の設定をします。

本番用の設定

まず、 @sap/xssecと@sap/xsenvという2つのモジュールをインストールします。
※以下のブログによると@sap/xssecのバージョン3はまだサポートされていないのでバージョン2を指定したほうがよさそうです
CAP: Demystify User Authentication

npm install @sap/xssec@ @sap/xsenv

XSUAA設定用のファイルを生成します。

cds compile srv/ --to xsuaa > xs-security.json

プロジェクトのルートにxs-security.jsonファイルができます。
image.png
中身はサービスの内容を参照して自動的に生成されています。

xs-security.json
{
  "xsappname": "products",
  "tenant-mode": "dedicated",
  "scopes": [
    {
      "name": "$XSAPPNAME.admin",
      "description": "admin"
    }
  ],
  "attributes": [],
  "role-templates": [
    {
      "name": "admin",
      "description": "generated",
      "scope-references": [
        "$XSAPPNAME.admin"
      ],
      "attribute-references": []
    }
  ]
}

package.jsonのcdsセクションを以下のように設定します。

package.json
  "cds": {
    "requires": {
      "db": {
        "kind": "hana"
      },
      "uaa": {
        "kind": "xsuaa"
      }
    }
  }

mta.yamlにXSUAAの設定を追加します。全体は以下のようになります。

mta.yaml
## Generated mta.yaml based on template version 0.2.0
## appName = products
## language=nodejs; multiTenant=false
## approuter=
_schema-version: '3.1'
ID: products
version: 1.0.0
description: "A simple CAP project."
parameters:
  enable-parallel-deployments: true


build-parameters:
  before-all:
   - builder: custom
     commands:
      - npm install
      - npx cds build

modules:
 - name: products-srv
   type: nodejs
   path: gen/srv
   properties:
     EXIT: 1  # required by deploy.js task to terminate 
   requires:
    - name: products-db
    - name: products-uaa
   provides:
    - name: srv-binding      # required by consumers of CAP services (e.g. approuter)
      properties:
        srv-url: ${default-url}

 - name: db
   type: hdb
   path: gen/db  
   parameters:
     app-name: products-db  
   requires:
    - name: products-db
    - name: products-uaa


resources:
 - name: products-db
   type: com.sap.xs.hdi-container
   parameters:
     service: hanatrial  # or 'hanatrial' on trial landscapes
     service-plan: hdi-shared
   properties:
     hdi-service-name: ${service-name}
 - name: products-uaa
   type: org.cloudfoundry.managed-service
   parameters:
     service: xsuaa
     service-plan: application
     path: ./xs-security.json      

ビルド&デプロイします。
ビルド: mbt build
デプロイ:cf deploy mta_archives/products_1.0.0.mtar

この結果、Cloud FoundryにXSUAAサービスのインスタンスが登録されます。
image.png

Cloud Fondry環境でサービスを実行するとどうなるか試してみます。
image.png
特別な権限のいらないProductサービスの方を実行してみます。
image.png
Unauthorized、つまり「認証されていませんよ」というエラーになりました。
image.png
認証のための入り口が必要ということで、Approuterを追加します。

Approuterを追加

プロジェクトのルートにapprouterフォルダを追加します。approuterの中に2つのファイル:package.jsonとxs-app.jsonを追加します。
image.png

approuter/package.json
{
    "name": "app-router",
    "description": "Node.js based application router service",
    "engines": {
        "node": "^8.0.0 || ^10.0.0"
    },
    "dependencies": {
        "@sap/approuter": "6.8.0"
    },
    "scripts": {
        "start": "node node_modules/@sap/approuter/approuter.js"
    }
}

mta.yamlにapprouterのモジュールを追加します。

mta.yaml
modules:
 - name: products-app-router
   type: approuter.nodejs
   path: approuter
   parameters:
     disk-quota: 256M
     memory: 256M
   requires:
     - name: products-uaa
     - name: srv-binding
       group: destinations
       properties:
         name: srv-binding
         url: "~{srv-url}"
         forwardAuthToken: true  

Approuterモジュールは、products-srvのURL(srv-url)をdestinationとして使用します。
image.png
xs-app.jsonを以下のように設定します。
Approuterにリクエストが入ってきたらsrv-bindingという宛先に流すという意味です。authenticationMethodが"route"となっているので、デフォルトでXSUAAを使用した認証が行われます。

approuter/xs-app.json
{
    "authenticationMethod": "route",
    "routes": [
        {
            "source": "^/(.*)",
            "destination": "srv-binding"
        }
    ]
}

ここまで設定できたら再度ビルド&デプロイします。

Approuterのアプリケーションを実行します。
image.png
ログインを求められるので、Cloud PlatformのユーザIDとパスワードを入力します。
image.png
まずは、特別な権限のいらないProductサービスを実行します。
image.png
データが表示されました。
image.png
次に、Adminサービスを実行してみます。こちらは権限なしエラーになります。
image.png

ユーザに権限を割り当て

Approuterサービスの中を見てみると、"admin"というロールができています。このロールをロールコレクションに割り当てたあと、自分のユーザに割り当てます。
image.png
Approuterのアプリケーションから一旦ログアウトして再度ログオンします。今度はAdminサービスも照会できました。
image.png

ここまでで、Cloud Foundry上の認証と権限の動作が確認できました。

ローカル実行用の設定

ローカル実行でもXSUAAを使用した認証が行われるようにします。

まず、ローカル実行時にsqliteのDBを見に行くようにpackage.jsonの設定を変更します。開発環境では[development]に指定した設定が優先されます。(※これは認証とは関係なく、単にローカル実行用の設定です)

package.json
  "cds": {
    "requires": {
      "db": {
        "kind": "hana"
      },
      "uaa": {
        "kind": "xsuaa"
      }
    },
    "[development]": {
      "requires": {
        "db": {
          "kind": "sqlite"
        }
      }
    }
  }

次に、.cdsrc.jsonファイルで認証のstrategyを以下のように変更します。これで、モックの認証ではなくJWTトークンを使用した認証が行われるようになります。

.cdsrc.json
{
    "auth": {
        "passport": {
            "strategy": "JWT"
        }
    }
}

approuter配下にdefault-env.jsonとdefault-services.jsonという2つのファイルを作成します。
image.png

default-env.jsonでは、srv-bindingが示すローカルの宛先を指定します。

approuter/default-env.json
{
    "destinations": [
        {
            "name": "srv-binding",
            "url": "http://localhost:4004",
            "forwardAuthToken": true
        }
    ]
}

default-services.jsonには、XSUAAサービスインスタンスからコピーしたcredentailsの情報を貼り付けます。
image.png

approuter/default-services.json
{
    "uaa": {
        "tenantmode": "dedicated",
        "sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",
        "clientid": "sb-products!t50054",
        "xsappname": "products!t50054",
        "clientsecret": "Hup0idYvmxnqSr3QXzR6bmq8j4c=",
        "url": "https://mio-trial.authentication.eu10.hana.ondemand.com",
        "uaadomain": "authentication.eu10.hana.ondemand.com",
        "verificationkey": "-----BEGIN PUBLIC KEY-----<public key>-----END PUBLIC KEY-----",
        "apiurl": "https://api.authentication.eu10.hana.ondemand.com",
        "identityzone": "mio-trial",
        "identityzoneid": "18954ba6-87b9-4059-ba9a-71bc85aa6889",
        "tenantid": "18954ba6-87b9-4059-ba9a-71bc85aa6889"            
    }
}

同じ内容を、プロジェクトルートに作成したdefault-services.jsonファイルにも設定します。このファイルは、ローカル実行の際にJWTトークンを検証するために使われます。先頭の"uaa"の部分は"xsuaa"に変更します。

default-services.json
{
    "xsuaa": {
        "tenantmode": "dedicated",
        "sburl": "https://internal-xsuaa.authentication.eu10.hana.ondemand.com",
        "clientid": "sb-products!t50054",
        "xsappname": "products!t50054",
        "clientsecret": "Hup0idYvmxnqSr3QXzR6bmq8j4c=",
        "url": "https://mio-trial.authentication.eu10.hana.ondemand.com",
        "uaadomain": "authentication.eu10.hana.ondemand.com",
        "verificationkey": "-----BEGIN PUBLIC KEY-----<public key>-----END PUBLIC KEY-----",
        "apiurl": "https://api.authentication.eu10.hana.ondemand.com",
        "identityzone": "mio-trial",
        "identityzoneid": "18954ba6-87b9-4059-ba9a-71bc85aa6889",
        "tenantid": "18954ba6-87b9-4059-ba9a-71bc85aa6889"            
    }
}

以上の設定ができたらローカルで実行してみましょう。プロジェクトルートでcds watchを実行してサービスを立ち上げ、approuterフォルダに移動してnpm startでApprouterを起動します。
Approuterはポート5000で実行されるので、ブラウザにlocalhost:5000と入力します。
image.png
するとまず、認証画面にリダイレクトされます。
image.png
Approuterのログを見ると、OAuthサーバにコードを要求しています。
image.png
認証されるとサービスにアクセスできます。
image.png
権限が必要なAdminサービスも照会できます。(自分のユーザにロールコレクションを割り当てているため)
image.png
Approuterのログには認証されてからのトークンのやりとりについては表示されませんでした。

認証されたユーザの情報を見てみる

せっかくなので、認証されたユーザの情報をログに表示させてみましょう。

srvフォルダの中にproduct-service.jsというファイルを作成します。以下はProductサービスのProductsエンティティに対するREADリクエストの前に呼ばれるハンドラです。リクエストコンテキストにはuserというオブジェクトがあり、ユーザに関する情報を持っています。

srv/product-service.js
module.exports = function (){
  this.before ('READ','Products', (req)=>{
    console.log(req.user);
  })
}

ブラウザからProductsサービスを呼ぶと、コンソールにログインしているユーザの情報が表示されます。

GET /product/Products
{
  id: 'xxx@gmail.com',
  name: { givenName: 'Mio', familyName: 'Yasutake' },
  emails: [ { value: 'xxx@gmail.com' } ],
  valueOf: [Function],
  toString: [Function],
  is: [Function],
  has: [Function],
  locale: 'en'
}

この情報を使って、DBに書き込む前にユーザの情報で項目を埋めることも可能です。(以下は架空のエンティティです)

  this.before ('CREATE','Orders', (req)=>{
    req.data.buyeremail = req.user.id
  })  

おわりに

CAPで作成したサービスにモックの認証、XSUAAによる認証を追加する手順について見てきました。CAPではこのように段階的に認証の手段を変えることができます。本番に近い環境でテストできるのは便利ですね。

参考

ブログ

ドキュメント

7
2
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
7
2