はじめに
この記事は、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を追加します。
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にルーティングします。
approuter/xs-app.json
{
"welcomeFile": "index.html",
"authenticationMethod": "route",
"routes": [
{
"source": "^/node-pg/",
"target": "/",
"authenticationType": "xsuaa",
"destination": "srv-api"
}
]
}
welcolmeFile(index.html)は、approuter/resourcesの中に作成します。これはApprouterのURLを叩いたときに最初に表示されるページです。
approuter/resources/index.html
<body>
Welcome to App Router default web page <br>
</body>
1.4 mta.yamlの調整
mta.yamlにApprouterモジュールを追加します。
mta.yaml
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の設定用のファイルです。
xs-security.json
{
"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セクションは以下のようになります。
srv/package.json
"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に以下のコードを追加します。
srv/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を追加します。
mta.yaml
- 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を直接叩いた場合はアクセスできなくなっています。
Approuterを経由した場合はアクセス可能です。
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用のスコープとそれらのスコープを持つロールテンプレートを定義します。
xs-security.json
{
"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でチェックします。
approuter/xs-app.json
{
"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
products始まりでないURIの場合(2つ目のルート)
スコープのチェックは行わない
3.3 ビルド、デプロイ
ビルド、デプロイした後、シークレットモードでApprouterのURLにアクセスします。
/node-pg/productsにアクセスすると、Forbiddenとなります。
/node-pg/products/1も同様です。
/node-pg/にはアクセスできます。
3.4 ユーザにロールを割り当て
デプロイの結果、Approuterのアプリケーションに対して2つのロールテンプレートが作成されています。まずは、Viewerロールを自分のユーザに割り当てます。
Security>Role Collectionsのメニューからロールコレクションを追加します。
ロールコレクションにViewerロールと、自分のユーザを割り当てます。
ロールを割り当てた後、シークレットウインドウからログインしなおし、/node-pg/productsにアクセスしてみます。
/node-pg/products/1にもアクセスできます。
POST、PUT、DELETEについてはブラウザからテストができないため、UIを作成してから確認することにします。
まとめ
この記事では、以下を実施しました。
Approuterを使用して認証を行う
Approuterを介さないサービスへのアクセスをブロックする
CRUD処理に必要な権限をチェックする
次回はサービスを使用したUIを作成したいと思います。
↧