Technical Articles
CFでApp Router, XSUAAとJava Application開発(SAP Cloud SDK使用)
App RouterとXSUAA を使ったJava Application開発を試してみました。以下の図のアーキテクチャを作ります。
チュートリアル「Secure Your Application on SAP Cloud Platform Cloud Foundry」を参考にしています。複数のサービスを使うので、理解にしにくく時間がかかりました。
開発環境
開発環境はJavaとNode.jsで分けています。既存のJava開発環境がWindowsにあったため継続して使っているだけで、深い理由はないです。
Java開発環境(Windows)
Javaは以下の環境で実行しています。
- OS: Windows10 64-bit
- openJDK: 1.8.0_242
- Chocolatey: 0.10.15
- maven: 3.6.3
- IDE: IntelliJ IDEA Community Edition 2019.3.3
- CF cli: 6.51.0+2acd15650.2020-04-07
- SAP Cloud SDK for Java: 3.18.1
※ CF cliは最新にしておきましょう。古いバージョンだと、manifest.ymlの情報が”cf push”で正しく送られないことがあるようです。このせいで半日ほど試行錯誤しました。
Node.JS開発環境(Ubuntu)
Node.jsは以下の環境で実行しています。
- OS: Ubuntu18.04.01 LTS
- nvm: 0.35.3
- Node.js: 12.16.2
- npm: 6.14.4
- SAP Cloud SDK for JavaScript:1.19.0
- SAP Cloud SDK cli: 0.1.8
- nest cli: 7.1.2
- CF cli: 6.51.0+2acd15650.2020-04-07
手順
1. Java Application作成
Windows環境で実行しています。
プロジェクトを置くディレクトリで以下のコマンドでプロジェクト作成(コマンド実行でフォルダが生成されます)。
mvn archetype:generate "-DarchetypeGroupId=com.sap.cloud.sdk.archetypes" "-DarchetypeArtifactId=scp-cf-tomee" "-DarchetypeVersion=RELEASE"
途中のプロンプトでは以下を入力。”artifactId”に入力した”test-sec”がApplication名です。
- groupId: com.sap.cloud.sdk
- artifactId: test-sec
- version: 1.0-SNAPSHOT
- package: com.sap.cloud.sdk
パッケージ生成
mvn clean package
ディレクトリを移動してtomee起動。
cd application && mvn tomee:run
ブラウザで”localhost:8080/hello”を開くと”Hello World!”が表示されます。
tomeeを止めて、プロジェクトのルートディレクトリに戻り、CFにデプロイします(CFにはログイン済み)。
cd .. && cf push
デプロイに成功したらブラウザでhttps://<host.domain>/hello”を開いて確認。
2. XSUAAとApp Router作成
Ubuntuで実行。
2.1. cliでApp Routerをローカルに作成
SAP Cloud SDK for Javascriptのcliを使ってApp Routerを作成します。
作成したいディレクトリに移動して以下のコマンド実行。
$ sap-cloud-sdk add-approuter
No 'manifest.yml' found.
Enter project name as maintained in Cloud Foundry: test-sec
Creating files
Successfully added approuter to your project.
Generated files might need customization. Documentation available here:
- xs-security.json (for help check https://help.sap.com/viewer/4505d0bdaf4948449b7f7379d24d0f0d/2.0.02/en-US/e6fc90df44464a29952e1c2c36dd9861.html)
- xs-app.json (for help check https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/c103fb414988447ead2023f768096dcc.html)
- mainfest.yml (for help check https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/ba527058dc4d423a9e0a69ecc67f4593.html)
途中のプロンプトではJava Applicationの名前”test-sec”を入力。
これで、固定名”approuter”というディレクトリが生成され、その中にApp Routerの内容が詰っています。lsコマンドで確認。
$ ls approuter -al
total 28
drwxr-xr-x 2 i348221 i348221 4096 Apr 30 11:46 .
drwxr-xr-x 14 i348221 i348221 4096 Apr 30 11:46 ..
-rw-r--r-- 1 i348221 i348221 448 Apr 30 11:46 manifest.yml
-rw-r--r-- 1 i348221 i348221 87 Apr 30 11:46 .npmrc
-rw-r--r-- 1 i348221 i348221 158 Apr 30 11:46 package.json
-rw-r--r-- 1 i348221 i348221 123 Apr 30 11:46 xs-app.json
-rw-r--r-- 1 i348221 i348221 57 Apr 30 11:46 xs-security.json
※.npmrcで変な値が設定され、AppRouterをCFにデプロイ時にエラーが起きたことがありました。.npmrcファイルの中身は以下であるべきです。
@sap:registry=https://npm.sap.com/
2.2. XSUAAインスタンスを作成
cliで生成された”xs-security.json”を変更します。
今回は暫定的にSingle Tenant にするので、”tenant-mode”をshared から dedicatedに変更。詳しくはチュートリアルやヘルプ文書などを参照。sharedだとXSUAAサービスがグローバルで作られてしまい、うまく認証ができなかったのでdedicatedにしています(どちらにすべきかは調べていないです)。
{
"xsappname": "test-sec",
"tenant-mode": "dedicated"
}
“xs-security.json”を使ってXSUAAインスタンスを作成。
cf create-service xsuaa application test-sec-xsuaa -c xs-security.json
cf cliでサービスを確認。
$ cf services
name service plan bound apps last operation broker upgrade available
abap abap-trial shared create succeeded sm-abap-trial-broker-3e22f640-a893-497a-a56a-01d090a4cbb7
test-sec-xsuaa xsuaa application create succeeded sm-xsuaa-9ef36350-f975-4194-a399-54db361e79b5
$ cf service test-sec-xsuaa
name: test-sec-xsuaa
service: xsuaa
tags:
plan: application
description: Manage application authorizations and trust to identity providers.
documentation: https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/6373bb7a96114d619bfdfdc6f505d1b9.html
dashboard:
service broker: sm-xsuaa-9ef36350-f975-4194-a399-54db361e79b5
Showing status of last operation from service test-sec-xsuaa...
status: create succeeded
message:
started: 2020-04-30T02:59:02Z
updated: 2020-04-30T02:59:02Z
There are no bound apps for this service.
XSUAAをJava Applicationにbindします。
cf bind-service test-sec test-sec-xsuaa
既にXSUAAサービスがある場合は、上記のタイミングでxs-security.jsonを使います。
cf bind-service test-sec test-sec-xsuaa -c xs-security.json
bindを反映させるため、restageしておきます。これでJava applicationとXSUAAインスタンスが繋がりました。
cf restage test-sec
2.3. App Router用Route作成
App Router用のRouteを作成します。
“dev”の部分はcfのスペース名です。
cf create-route dev cfapps.eu10.hana.ondemand.com --hostname <App Routerのホスト名>
2.4. App Router作成
次にApp Routerの”manifest.yml”を更新します。
env -> destinationsのurlはステップ1で作成したJava Applicationのエンドポイントです。
applications:
- name: test-sec-approuter
routes:
- route:
https://<2.3で作成したRouteのホスト名>.<2.3で作成したRouteのドメイン名>
path: .
memory: 128M
buildpacks:
- nodejs_buildpack
env:
# TENANT_HOST_PATTERN: >-
# "test-sec-(.*).cfapps.sap.hana.ondemand.com"
destinations: >-
[{"name":"test-sec","url":"https://<1で作成したJavaのホスト・ドメイン名>","forwardAuthToken":true}]
services:
- test-sec-xsuaa
※YAMLの規則で”>-“を使うと改行をスペースに置換するようです。”-“は最終行の改行も削除という意味らしい。
cf pushします。
cf push
これで、XSUAA, App Router, Java Applicationが繋がりました。
App Routerのエンドポイント”https://<2.3.で登録したRouteのエンドポイント>/hello”をブラウザで開くと認証画面へリダイレクトされ、認証完了するとJava Application画面が開きます。
しかし、この時点ではまだ直接Java Applicationのエンドポイントを認証なしで開くことができます。
3. Java Applicationにセキュリティ実装
WIndowsで実行
3.1. manifest.yml変更
“manifest.yml”を変更しておきます。
Routeがランダムだったものを固定にし、XSUAAサービスと紐付けます。
---
applications:
- name: test-sec
memory: 1024M
timeout: 300
random-route: false
routes:
- route:
https://<1でランダムに登録されたRoute>
path: application/target/test-sec-application.war
buildpacks:
- sap_java_buildpack
env:
TARGET_RUNTIME: tomee7
SET_LOGGING_LEVEL: '{ROOT: INFO, com.sap.cloud.sdk: INFO}'
JBP_CONFIG_SAPJVM_MEMORY_SIZES: 'metaspace:128m..'
services:
# - my-application-logs
- test-sec-xsuaa
# - my-destination
# - my-connectivity
3.2. web.xml変更
“application/src/main/webapp/WEB-INF/web.xml”を変更します。SAP Cloud SDK for Javaを使うとコメントアウトされている以下の部分があり、その箇所を有効にします。
<login-config>
<auth-method>XSUAA</auth-method>
</login-config>
<security-constraint>
<web-resource-collection>
<web-resource-name>Baseline Security</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>Display</role-name>
</security-role>
この状態でパッケージ生成をすると、サーブレットにアクセスできません。直接Java Applicationにアクセスすると401(Unauthorized)エラー。App Router経由でユーザ認証してアクセスしても403(Forbiden)エラーとなります。
ちなみに<login-config>部分だけを有効にした場合は、直接Java Applicationにアクセスできます。
3.3. サーブレット変更
サーブレットでデコレータ””@ServletSecurity”を使ってセキュリティの実装をします。今回は、SAP Cloud SDK for Javaで最初に生成される”HelloWorldServlet.java”をそのまま使っています(ディレクトリ”application/src/main/java/com/sap/cloud/sdk”)。
ソース全体は以下の通りです。
package com.sap.cloud.sdk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
@ServletSecurity(@HttpConstraint(rolesAllowed = { "Display" }))
public class HelloWorldServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(HelloWorldServlet.class);
@Override
protected void doGet( final HttpServletRequest request, final HttpServletResponse response )
throws IOException
{
logger.info("I am running!");
response.getWriter().write("Hello World!");
}
}
3.4. CFデプロイ
あとはパッケージ生成してCloud Foundryにデプロイします。
これで、直接Java Applicationにアクセスすると401(Unauthorized)エラー。App Router経由でユーザ認証してアクセスしても403(Forbiden)エラーとなります。
# From Application Directory
mvn clean package
# move to Root Directory and push
cd .. && cf push
4. ロール定義
4.1. XSUAA更新
Ubuntuで実行。
XSUAAにscopesとrole-templatesの情報を追加します。追加対象のファイルは”xs-security.json”です。
「2.2. XSUAAインスタンスを作成」のタイミングで追加していても大丈夫です。
※“role-collections”を作れば、後続の「4.2.Role Collection追加」が不要でした(2020/8/26追記)。
{
"xsappname": "test-sec",
"tenant-mode": "dedicated",
"scopes": [
{
"name": "$XSAPPNAME.Display",
"description": "display"
}
],
"role-templates": [
{
"name": "Viewer",
"description": "Required to view things in our solution",
"scope-references" : [
"$XSAPPNAME.Display"
]
}
]
}
これでXSUAAサービスをアップデートします。
cf update-service test-sec-xsuaa -c xs-security.json
4.2. Role Collection追加
SAP Cloud Platform CockpitからRole Collectionを追加します。Subaccountを開いて、メニューからSecurity -> Role Collectionsで”New Role Collection”ボタンを押します。
NameとDescriptionを入力して保存。
4.3. Role追加
先のステップで追加したRole Collection詳細画面で”Add Role”ボタンを押します。
Application IdentifierにJava Applicationを選択(“!”以降は何を表しているかわかりませんでしたが、GUIDみたいなものと推測)。
Role TemplateとRoleを選択(ステップ「4.1. XSUAA更新」で追加したRole Templateが表示されます)。
4.4. Role Collectionのユーザ割当
SAP Cloud Platform CockpitからRole Collectionをユーザに割り当てます。
Subaccountを開いて、メニューからSecurity -> Trust Configurationでデフォルト設定を選択。
割当したいユーザを選択して「Show Assignments」ボタンを押します。
「Assign Role Collection」ボタンを押します。
ステップ「4.2. Role Collection追加」で登録したRole Collectionを選びます。
これでApp Router経由だとアクセスできるようになり、直接Java Applicationはアクセスできなくなりました(401 Unauthorized)!
App Rotuerのエンドポイントにアクセスすると以下の順序でJava Applicationに繋がります。
- App Routerのmanifest.ymlで指定したroute
- App RouterがbindしているXUSAAサービスで認証し、Tokenを受け取る
- App Routerのxs-app.jsonに記載されているsourceパスからtargetとdestinationを取得
- App Routerのmanifest.ymlに記述した環境変数のdestinationに対応するurlへ遷移(多分Destinationサービスを見に行く形でもOk)