##はじめに
この記事は、Node.jsとPostgreSQLでシンプルなアプリケーションを作成するシリーズの3回目です。
##この記事のゴール
- Approuterを使用して認証を行う
- Approuterを介さないサービスへのアクセスをブロックする
- CRUD処理に必要な権限をチェックする
##ステップ
- Approuterを使用して認証を行う
- Approuterを介さないサービスへのアクセスをブロックする
- CRUD処理に必要な権限をチェックする
###1. Approuterを使用して認証を行う
####1.1. Approuterモジュールを追加
プロジェクト直下にapprouterというフォルダを追加します。
node-postgres-sample
└ approuter
└ srv
└ mta.yaml
####1.2. approuter/package.jsonの設定
approuterフォルダにpackage.jsonを追加します。
{
"name": "approuter",
"version": "1.0.0",
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
},
"dependencies": {
"@sap/approuter": "^10.4.0"
}
}
####1.3. approuter/xs-app.jsonの設定
approuterフォルダにxs-app.jsonファイルを追加します。このファイルによって入ってきたリクエストをNode.jsアプリのURLにルーティングします。
{
"welcomeFile": "index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/node-pg/",
"target": "/",
"authenticationType": "xsuaa",
"destination": "srv-api"
}
]
}
welcolmeFile(index.html)は、approuter/resourcesの中に作成します。これはApprouterのURLを叩いたときに最初に表示されるページです。
<body>
Welcome to App Router default web page <br>
</body>
####1.4 mta.yamlの調整
mta.yamlにApprouterモジュールを追加します。
modules:
- name: node-postgres-sample-approuter
type: approuter.nodejs
path: approuter
parameters:
disk-quota: 512M
memory: 512M
requires:
- name: node-postgres-sample-uaa
- name: srv-api
group: destinations
properties:
name: srv-api
url: "~{srv-url}"
forwardAuthToken: true
resourcesセクションにXSUAAサービスを追加します。
- name: node-postgres-sample-uaa
type: org.cloudfoundry.managed-service
parameters:
path: ./xs-security.json
service-plan: application
service: xsuaa
プロジェクト直下にxs-security.jsonを作成します。これはXSUAAの設定用のファイルです。
{
"xsappname": "node-postgres-sample",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "uaa.user",
"description": "UAA"
}
],
"role-templates": [
{
"name": "Token_Exchange",
"description": "UAA",
"scope-references": [
"uaa.user"
]
}
]
}
####1.5 ビルド、デプロイ
ビルド、デプロイした後、シークレットモードでApprouterのURLにアクセスします。
ログインを求められるので、XSUAAを使用した認証が有効になっていることが確認できます。
ログインすると、Welcomeページが表示されます。
URLの末尾を/node-pg/products
とすると、productsのデータにアクセスできます。これでNode.jsのアプリケーションにリクエストが転送されていることが確認できました。
もともとのサービスのURLをシークレットモードで開いてみます。
こちらは認証なしでアクセスできます。現状、直接サービスのURLを叩いた場合は認証されていないユーザでもアクセスできる状態です。
###2. Approuterを介さないサービスへのアクセスをブロックする
####2.1. Dependencyを追加
srv
フォルダに移動し、以下のDependencyを追加します。
npm i @sap/xssec passport
- @sap/xssec: HTTPヘッダに渡されたアクセストークンを検証したり、認証情報にアクセスするためのモジュール
- passport: 認証を行うためのミドルウェア。任意のStrategyを利用して認証を行うことができる
package.jsonのdependenciesセクションは以下のようになります。
"dependencies": {
"@sap/xsenv": "^3.1.0",
"@sap/xssec": "^3.2.1",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"passport": "^0.4.1",
"pg-promise": "^10.10.2"
}
####2.2. passportを使用したトークンチェックの追加
server.jsに以下のコードを追加します。
'use strict';
const express = require('express')
const bodyParser = require('body-parser')
//追加--------------------------------------------------
const passport = require('passport')
const JWTStrategy = require('@sap/xssec').JWTStrategy
const xsenv = require('@sap/xsenv')
//------------------------------------------------------
const dbConn = require('./db-conn')
const dbOp = require('./db-op')
var _db = undefined
const app = express()
app.use(bodyParser.json())
//追加--------------------------------------------------
passport.use(new JWTStrategy(xsenv.getServices({xsuaa:{tag:'xsuaa'}}).xsuaa));
app.use(passport.initialize());
app.use(passport.authenticate('JWT', { session: false }));
//------------------------------------------------------
リクエストヘッダにJWTトークンが存在し、認証が成功すると以下のオブジェクトにアクセス可能になります。これらのオブジェクトを追加の権限チェック(たとえば、ユーザが指定したスコープを持っているか)に使うことができます。(@sap/xssecの"Usage with Passport Strategy"セクションを参照)
オブジェクト | 説明 |
---|---|
request.user | ユーザのID(id)、名前(name)、メールアドレス(emails)などの情報 |
request.authInfo | Security Contextのオブジェクト。利用可能なメソッドについてはリンク先の"API Description"のセクションを参照。 |
request.tokenInfo | TokenInfoオブジェクト(※)。トークンの情報にアクセスできる。 |
※ドキュメントのTokenInfoオブジェクトに関するリンクが切れていたので、持っているメソッドについて確認した。結果は以下の通り。
- reset
- isDecoded
- isValid
- getErrorObject
- getTokenValue
- getHeader
- getPayload
- getExpirationDate
- getIssuedAt
- getIssuer
- getSubject
- getAudiencesArray
- getUserId
- getZoneId
- getClientId
- isTokenIssuedByXSUAA
- verify
####2.3 mta.yamlの調整
node-postgres-sample-srv
のrequresセクションにnode-postgres-sample-uaaを追加します。
- name: node-postgres-sample-srv
type: nodejs
path: srv
provides:
- name: srv-api
properties:
srv-url: ${default-url}
build-parameters:
ignore: ["node_modules/"]
requires:
- name: cap-posgre-sample-db
- name: node-postgres-sample-uaa
####2.3 ビルド、デプロイ
ビルド、デプロイした後、シークレットモードでサービスのURLにアクセスします。
Unauthorizedとなりました。サービスのURLを直接叩いた場合はアクセスできなくなっています。
###3. CRUD処理に必要な権限をチェックする
サービスで実行可能な操作は以下の通りです。各操作に対し、必要な権限(スコープ)を設定します。
URI | HTTPメソッド | 操作 | 必要な権限(スコープ) |
---|---|---|---|
/ | GET | "Hello!"を表示 | - |
/products | GET | すべてのproductを表示 | DisplayまたはUpdate |
/products/:id | GET | 指定したidのproductを表示 | DisplayまたはUpdate |
/products | POST | productを登録 | Update |
/products | PUT | productを変更 | Update |
/products | DELETE | productを削除 | Update |
####3.2. xs-security.jsonを更新
xs-security.jsonでDisplay、およびUpdate用のスコープとそれらのスコープを持つロールテンプレートを定義します。
{
"xsappname": "node-postgres-sample",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "$XSAPPNAME.Display",
"description": "Display Products"
},
{
"name": "$XSAPPNAME.Update",
"description": "Update Products"
}
],
"role-templates": [
{
"name": "Viewer",
"description": "View Products",
"scope-references": [
"$XSAPPNAME.Display"
]
},
{
"name": "Manager",
"description": "Maintain Products",
"scope-references": [
"$XSAPPNAME.Display",
"$XSAPPNAME.Update"
]
}
]
}
####3.3. approuter/xs-app.jsonを更新
特定の操作(URIとHTTPメソッドの組み合わせ)に対して、ユーザが所定のスコープを持っているかどうかをApprouterでチェックします。
{
"welcomeFile": "index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/node-pg/products(.*)$",
"target": "/products$1",
"authenticationType": "xsuaa",
"destination": "srv-api",
"scope": {
"GET": ["$XSAPPNAME.Display", "$XSAPPNAME.Update"],
"default": "$XSAPPNAME.Update"
}
},
{
"source": "^/node-pg/",
"target": "/",
"authenticationType": "xsuaa",
"destination": "srv-api"
}
]
}
products
で始まるURIとそれ以外で権限チェックの有無が変わるので、ルートを分けています。
-
products
で始まるURIの場合(1つ目のルート)- GETリクエストに対して、ユーザが
$XSAPPNAME.Display
または$XSAPPNAME.Update
のスコープを持っているかチェックする・・・scope.GET - それ以外のHTTPメソッドに対しては
$XSAPPNAME.Update
のスコープを持っているかをチェックする・・・scope.default
- GETリクエストに対して、ユーザが
-
products
始まりでないURIの場合(2つ目のルート)- スコープのチェックは行わない
####3.3 ビルド、デプロイ
ビルド、デプロイした後、シークレットモードでApprouterのURLにアクセスします。
/node-pg/products
にアクセスすると、Forbiddenとなります。
####3.4 ユーザにロールを割り当て
デプロイの結果、Approuterのアプリケーションに対して2つのロールテンプレートが作成されています。まずは、Viewerロールを自分のユーザに割り当てます。
Security>Role Collectionsのメニューからロールコレクションを追加します。
ロールコレクションにViewerロールと、自分のユーザを割り当てます。
ロールを割り当てた後、シークレットウインドウからログインしなおし、/node-pg/products
にアクセスしてみます。
/node-pg/products/1
にもアクセスできます。
POST、PUT、DELETEについてはブラウザからテストができないため、UIを作成してから確認することにします。
##まとめ
この記事では、以下を実施しました。
- Approuterを使用して認証を行う
- Approuterを介さないサービスへのアクセスをブロックする
- CRUD処理に必要な権限をチェックする
最終回となる次回はサービスを使用したUIを作成したいと思います。
##参考