7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【2019年12月版】Quarkus、Azure Functionsに載せるは天国。AD B2C で OIDC は地獄。

Last updated at Posted at 2019-12-08

Azure Functions に上げた Quarkus の Rest API を AD B2C でセキュアにしたい

最近のお気に入り Quarkus ですが、今回は Azure Functions に上げてみたいと思いまして。コンテナデプロイできるのもいいけど、やっぱりサーバレスだよね?ってことで挑戦してみましたが、かなりあっさり出来てしまいました。
これじゃ記事にならないじゃん。。。ということで、Keycloak でも簡単に SSO できたし、"AD B2C"ってので OpenID Connect がさっくりできるらしいからやってみようか、と挑戦してみましたが。。。
…想像以上の地獄でしたw

本記事の概要

今回は以下の流れでチャレンジを行います。

  1. Quarkus のサンプルをAzure Functionsにデプロイして動作確認
  2. AD B2C リソースの作成と設定
  3. Postmanを使用してAD B2CからOIDCのトークンを取得
  4. 取得したトークンを使ってFunctionsにデプロイしたQuarkusにアクセス

ちょっとSSO してみようという試みでしたが意外と壮大な計画になってしまいました。。。

対象環境

  • Java: OpenJDK 8.x
  • Maven: 3.6.2
  • surefire-plugin: 2.22.2
  • Quarkus: 1.0.1.Final
  • Azure CLI: 2.0.77
  • Postman: 7.13.0

今回は AD B2C など Azure の様々な要素が色濃く現れてまいります。Azure の全体的なサービスアーキテクチャについては以下のわかりやすい記事がありますので、これを眺めながらの方が理解できるかと思います。

ディレクトリとかテナントとかリソースグループとかサブスクリプションとか、わかりにくさ抜群のAzureですが、B2B2Cとしては本物です。(というかちゃんとマルチテナントに対応できているのはAzureぐらい?)
頑張って、ついていきましょう。。。

そして Quarkus のプロジェクトを Azure Fuctions にデプロイする際に az コマンドを使用しますので、"Azure CLI" が必要です。
以下のページを参考に Azure CLI をインストールしてください。

Quarkus は 1.0.1.Final です。遂に、正式版ですね。。。初めて触ってからここ数ヶ月のお付き合いですが、感慨深いです。

続いてOIDCトークンの確認に、"Postman" を使用します。もしなければ以下のサイトからインストールをお願いします。

また surefire-plugin のバージョンについてですが、quakusが生成したプロジェクトのビルドの途中で以下のようなエラーに遭遇したのでpom.xmlを手で修正してバージョンアップさせました。(前はうまく言っていたんだけどね・・・?)

それでは Azure Functions の AD B2C での SSO、チャレンジしてみましょう!

1. Quarkus のサンプルを Azure Functions に上げてみる。

まずはサクッと、上げてみたいと思います。以下のページの手順で、ほぼ大丈夫です。

1-1. プロジェクトの作成

以下のコマンドでプロジェクトを生成します。

$ mvn archetype:generate \
    -DarchetypeGroupId=io.quarkus \
    -DarchetypeArtifactId=quarkus-azure-functions-http-archetype \
    -DarchetypeVersion=1.0.1.Final

で、Azure上にデプロイするプロジェクトの情報を色々聞かれますので、チュートリアルにしたがって以下のように答えておきます。

  • groupId: org.acme
  • artifactId: quarkus-demo
  • version: デフォルトでOK
  • package: デフォルトでOK
  • appName: デフォルトでOK
  • appRegion: デフォルトでの"westus"でもOKというかお好みですが、近い方が良いので "japaneast" を指定してみます。
  • function: デフォルトでOK
  • resourceGroup: Functinosのインスタンスを生成する先のリソースグループを指定します。

前半は Maven プロジェクトのお決まりのやつですが、appNameappRegionresourceGroup はAzure Portal で作成する/されたものと一致してある必要あります。

また、ここの設定値は pom.xml に反映されますので、リージョンやリソースグループなどの設定値を変更したい場合は pom.xml の定義を修正すればOKです。

1-2. az のログイン

以下のコマンドで AZ CLI をログインさせ、操作対象のプロジェクトを決めておきます。

$ az login

ブラウザが立ち上がるのでAzureにログインをお願いします。
コンテナの中などブラウザ立ち上がらない状況では、"次のコード入れてね〜"という案内が出るので大丈夫です。

1-3. 関数アプリ、Functions App の作成

チュートリアルでは mvn clean install azure-functions:deploy でOKとありますが、自分の環境では何度やってもFunctionsの作成に失敗してしまいました。
先に Azure Portal から Functions Appを作成しておかないとデプロイができませんでした。
というわけで、上記で指定した設定通りの Functions App を作成します。

まずは Azure ポータルの"リソースの作成"をクリックして、"functions"を検索します。

スクリーンショット 2019-12-07 13.03.02.png

"Function App"がヒットしました。Classic でない方を選択します。

スクリーンショット 2019-12-07 13.03.30.png

はい、"関数アプリ"が出ました。"作成"をクリックします。(Azure FunctionsだったりFunction Appだったり関数アプリだったり、そのあたりの統一感がないのがね。。。)

スクリーンショット 2019-12-07 13.13.41.png

リソースグループに "resourceGroup"で指定した値、関数アプリ名に"appName"で指定した値、ランタイムスタックに "Java"を指定します。地域では "appRegion" で指定した地域を使用します。

"確認および作成"をクリックすると、設定に問題がないか確認画面が表示され、確認画面から"作成"ボタンクリックで初期状態のFunctionが作成されます。

この作成作業は数分かかります。しばらく待つと"リソースへ移動"ボタンが表示されるのでクリックすると作成された関数アプリの詳細画面に行けます。
そこで表示されているURLをクリックすると、以下の画面になると思います。
スクリーンショット 2019-12-07 13.16.39.png

はい、スタート画面が無事に表示されました!

1-4. デプロイ&確認

デプロイそのものは以下のコマンドで一発です。簡単。

$ mvn clean install azure-functions:deploy

"deploy" コマンドでは 'target' 以下のお掃除はしてくれませんので、ソースコードや構成に変更があった場合は毎度、clean install azure-functions:deploy の3点セットは実行した方が良いです。
単にネットワークエラーやログインエラーなどの"デプロイに失敗しただけ"の時は"azure-functions:deploy"でOKです。

ブラウザから関数アプリのアドレスに"/api/hello"を付けたURL、https://xxxxxxxxx.azurewebsites.net/api/hello のようなURLを開いてみましょう。

api_hello.png

・・・はい無事に hello jaxrs が表示され、動作確認完了です。
あっさりですが、これで REST APIやServlet、vert.x使ってリバプロ、なんてこともできてしまいます。あらイージー。。。

それでは、いよいよ AD B2C の世界へ踏み込んで参りましょう。。。

2. AD B2C の作成

上記でデプロイした関数アプリと同じリソースグループに "AD B2C"のリソースを作成します。このあたりの手順はやや複雑なので、公式ガイドをご覧ください。(これもわかりにくいけど…)

この手順は非常にわかりにくいので、ざっくり言いますと "一旦、新規にディレクトリ作成してから、サブスクリプションに割り当てる"の2段階の作業が必要です。
で、この2つの作業はどちらもリソースの新規作成→AD B2Cで実施します。つまり…

  1. リソースの新規作成→AD B2C作成→新しいテナントの作成
  2. リソースの新規作成→AD B2C作成→既存テナントのサブスクリプションにリンク

という手順を踏む必要があります。まじすか。。。

3. AD B2C の設定、の準備

続いては ローカルで quarkus の開発サーバーを動かしておき、そこへのアクセストークンをPostman で取得してみる、ということをゴールとします。

つまり、"サービスアプリ"は "ローカル"、"クライアントアプリ"は "Postman" という構成を AD B2C にて設定します。

AD B2C、というか AD はこのように設定作業の前に何のために何をするかをはっきりさせておかないと、設定の沼にハマります。ついつい目の前の設定値にのめり込んで闇雲なトライ&エラーに陥りがちです。

さて、以下の公式チュートリアルを参考に設定を行っていきたいと思いますが、初見では正直、何を言っているのか何をやっているのか初めはさっぱり掴めませんでした。。。

が、要するに "ロールじゃなくてスコープでアクセス制限するからね〜"ということです。サービス側では"スコープの定義"、クライアントでは"使用するスコープを許可してもらう"、という設定が必要です。(振り返って初めて理解したけどね。。。というか途中でクライアントアプリと言ったりユーザーと言ったりするから混乱する。)

それでは地獄の1丁目、いってみます!

4. アプリの登録 その1: "サービスアプリ"の登録

まずは "サービス用アプリ"としてhttp://localhost:8080 を登録します。

4-1. 作成

AD B2Cリソースを開きます。が、 AD B2C はリソースグループ内に新規にディレクトリを作成します。
まずは新規に作成したADB2Cリソースと同じ名前のディレクトリに移動しましょう。

ディレクトリ切り替えMicrosoft Azure.png

なんとかして AD B2C サービスの画面にたどり着いてください。そこで"アプリの登録(プレビュー)"をクリックします。

アプリ名は適当でOKですが、下の方にあるオプションで "Web API"を指定してください。

アプリケーションの登録 - Microsoft Azure.png

登録が完了したら、URLの追加をします。左のメニューから"認証"をクリック、表示されたページから"プラットフォームを追加"をクリックします。

URLの追加その1 - Microsoft Azure.png

以下は、続いて表示されるダイアログの"構成の選択"から"Web"を選択した後の画面です。ここで保護するサイトのURLを入力します。
Web の構成 - Microsoft Azure.png

いったん、http://localhost:8080 とローカルの開発サーバーを指しておきましょう。
"構成"をクリックで保存されます。

4-2. "スコープ"の追加

サービスを提供する側のアプリは"自分がどのようなスコープを使用するのか"を定義する必要があります。

左のメニューから"APIの公開"をクリックし、 "Scopeの追加"をクリックします。
API の公開 初期表示 - Microsoft Azure.png

初回は"アプリID URI の設定"ダイアログが立ち上がります。

スコープの 追加、URIの設定 - Microsoft Azure.png
これはそのまま"保存して続ける"でOKです。

スコープの 追加 - Microsoft Azure.png

とりあえずサンプルの通りに設定してみます。

そして、ここで表示されている "スコープ名" 下のURI、これが後で指定するスコープ名となります。これ、まじで地雷。

すでにお腹いっぱいな感じがしますが、これでようやく"サービス側アプリ"の登録が完了です。

5. アプリの登録 その2: "クライアントアプリ"の登録

続いてpostmanを登録します。

5-1. 作成

再び"アプリケーションの登録(プレビュー)"からPostman用のアプリケーション登録を行います。

クライアント側アプリケーションの登録 - Microsoft Azure (1).png
前回と違い、"URLを入力する欄"が表示されています。どうした? ここも http://localhost:8080 としておきます。

クライアント側アプリケーションの登録 - Microsoft Azure (1).png
登録ができると概要画面に行きます。ここでクライアントのIDをコピーしておきましょう。

5-2. "アクセス許可"の追加

"アクセス許可"とは、サービスアプリで提供される"スコープ"のうち、どのスコープでのアクセスをこのクライアントで許可するのか?というわけです。サービス側のアプリケーション登録で公開したスコープをクライアントに割り当てます。

Postman - API のアクセス許可 - Microsoft Azure.png

左側のメニューから"APIのアクセス許可"をクリックし、アクセス許可の構成画面を表示します

"アクセス許可の追加"ボタンをクリックします。

API アクセス許可の要求 - Microsoft Azure.png

"自分のAPI"から先ほど登録したサービス側のアプリを選択します。

API アクセス許可の要求その2 - Microsoft Azure (1).png

"委任されたアクセスの許可"を選択し、上記のSample App のアクセス許可から File.Read を選択して"アクセス許可の追加"をクリックします。

Postman - 管理者の同意 - Microsoft Azure (1).png

で、これに"管理者の同意を与えます"ボタンクリックです。
ログイン画面と同意のダイアログが表示されるので"承諾"ボタンをクリックしておきます。

Postman - API のアクセス許可 同意完了- Microsoft Azure (1).png
しばらく待つと一覧画面にチェックがついて委任の同意が得られたことがわかります。

クライアント側アプリの設定は以上です。

6. ユーザーフロー(ポリシー)の追加

続いてログインするユーザーの処理フローの追加を行います。

今回はユーザーの新規登録とログインが行える "SignUp_SignIn" のフローを使用します。(というかだいたいこれでOK)

6-1. 作成

AD B2Cのトップに戻り、左側のメニューから "ユーザーフロー(ポリシー)"をクリックします。

Azure AD B2C - ユーザー フロー  ポリシー  - Microsoft Azure.png

"新しいユーザーフロー"をクリックします。

ユーザー フローを作成する - Microsoft Azure.png

ユーザーフローから"サインアップとサインイン"をクリックします。

ユーザーフローの作成 - Microsoft Azure.png

名前を入力し、"Email signUp" にチェックを入れて "作成ボタン"をクリックします。名前は "SignUp_SignIn"の略で susi としておきます。

6-2. エンドポイントの確認方法

作成後の 一覧から"B2C_susi" を選んで詳細画面を表示します。

ユーザー フローを実行します - Microsoft Azure.png

ユーザーフローを実行しますボタンをクリックします。

..../v2.0/.well-known/openid-configuration をクリックしてブラウザで開きます。

issuerの設定をみてみましょう。

最後についている ?p=b2c_1_susi は大抵のクライアントじゃ処理できないと思います。(クエリで終わるURLを想定していない)

というわけで、次の"トークンの互換性の設定" が必要です。

6-3. トークンの互換性の設定

"プロパティ"をクリックします。
B2C_1_susi - プロパティ - Microsoft Azure.png

ページの中盤の"トークンの互換性設定"から tfpが含まれるhttps://<domain>/tfp/xxxxxxxxxxx/B2C_1_susi/v2.0 を選択し、"保存"をクリックする。

これによって issuer で表示される URL がパスにポリシー名が含まれた形式に変わります。これで様々なクライアントからアクセスできるようになります。

先ほどの.well-known/openid-configuration のアドレスをブラウザで開いてみましょう。issuer のアドレスがちょっと変わっていると思います。これ、後でも述べますが非常に重要なのです。

いや、ほんと、これはよく見つけたよね、と自分でも思う

ローカルを対象とするAzureの設定は一旦、以上です。

7. Postman からトークンの取得

いよいよ Postman の登場です!Postmanの起動〜ログインの流れは省略いたします。各位、よろしくお願いします。

7-1. トークンの取得

新規のリクエストを作成してから"Authrization"をクリック、Typeに"OAuth 2.0"を選択して次のような画面になっているというところからスタートします。

スクリーンショット 2019-12-07 14.29.22.png

"Get New Access Token" をクリックしてください。以下のようなダイアログが表示されると思います。

Postmanダイアログ.png

各パラメータに以下の値を設定します。

  • Token Name: 適当でOKです。
  • Callback URL : http://localhost:8080
  • Auth URL : 先ほどの .well-known/openid-configuration のjsonから得られる "authorization_endpoint" の URL。"b2c_1_susi/oauth2/v2.0/authorize" で終わるもの。
  • Client ID: Postman用に追加した AD B2CのアプリケーションのID
  • Scope: 例のScopeのURIをここで直打ちします。それと openidが必要です。https://xxxxxxxxxxx.onmicrosoft.com/xxxxxxxxx/File.Read openid のような目を疑う設定値となります。
  • State: 空白でOK
  • Client Authentication: Send client credentials in bodyを選択

で、"Request Token"をクリックします。

sute.jp などで適当なメールアドレスをゲットしておきましょう。Azureのログイン画面が表示されるので"Sign Up"からユーザー登録を行います。

無事にトークンがゲットできたでしょうか?
みんな、無事じゃないようですけど。。。

特にScopeの設定がびっくりです。また、authのアドレスも気をつけてください。?p=B2C_1_susiで終わるURLはPostmanでもエラーが出ますよ!

7-2. jwt.io で確認

ブラウザで jwt.io にアクセスして少しスクロールすると表示される "Debugger" の箇所を表示します。

左側の"Encoded"エリアに Postman で取得できたトークンをベタっと貼り付けると右側の "Decoded" エリアに中身の JSON が復元されます。

...
  "scp":"File.Read"
...

というのが確認できたでしょうか?この "scp" の値を Quarkus (というかJavaEE)的に "Role"として扱えばOKということになります。

8. Quarkus OIDC の追加

それでは Quarkus に OIDC 対応を追加していきます。以下のコマンドで Quarkus OIDC プライグインを追加します。

$ mvn quarkus:add-extension -Dextensions="oidc"

続いてsrc/main/resource/application.properties を以下の内容で作成します。

src/main/resources/application.properties
quarkus.oidc.auth-server-url=https://xxxxxxxxxx.b2clogin.com/tfp/xxxxxxxxxxxxx/b2c_1_susi/v2.0/
quarkus.oidc.authentication.scopes=https://xxxxxxx.onmicrosoft.com/xxxxxxxxxxxxx/File.Read openid
quarkus.oidc.client-id=xxxxxxxxxx
quarkus.oidc.roles.role-claim-path=scp

この設定もかなり手こずりましたが、注意点は以下の通りです。

  • auth-server-urlは issuer のURLを厳密にコピペする。最後の"/"も欠けてはいけない
  • scopes の scope は例のURI。
  • role-claim-path はjwt.ioで表示されたjsonのトップを起点とするパス。今回は"scp"だけで行けたので複雑にならず幸いだった。

で、メソッドの方に制約をつけます。

src/main/java/org/acme/GreetingResource.java
...
@Path("/api/hello")
@RolesAllowed("File.Read")
public class GreetingResource {
...

Quarkusの作業は一旦、以上とします。"/api/hello"がアクセス保護できればOKとします。

で、以下のコマンドでデプロイを行います。

$ mvn clean install azure-functions:deploy

9. Postman での確認

さて、実際の Azure Functions に対して Postman からのトークン付きアクセスを行ってみましょう。

9-1. AD B2C の設定変更

サービスアプリ、クライアントアプリそれぞれのリダイレクトURLを functions の方に向けておきます。
AD B2C の画面に戻り、"アプリケーションの登録(プレビュー)"からサービス側のアプリを選択し、"認証"をクリックします。

認証 リダイレクトURLの修正- Microsoft Azure.png

ここでリダイレクト先のURLを関数アプリのURL + "/api/hello" として、"保存"します。
これをPostman 用のアプリケーションでも実施します。

9-2. Postman でのアクセス

まずは、そのまま叩いてみましょう。

スクリーンショット 2019-12-07 12.00.23.png

401 エラーとなりました。トークンなしではアクセスできませんね。

続いて新規のトークンを取得してみましょう。先ほどと同様ですが、リダイレクト先を関数アプリのURL +"/api/hello" に変更し、トークンを取得してください。

トークンの取得後、以下のAdd auth data toRequest Headersが選択されていることを確認してください。Request URLではログインできませんでした。。
Postman 2019_12_27 14_54_30.png

それでは、トークンを使用してGETしてみます!

スクリーンショット 2019-12-07 12.05.00.png

ステータスが200hello jaxrsのレスポンスが帰ってきました! @RolesAllowed("File.Read") のアノテーション、ちゃんと通過できました!!

お疲れ様でした!

まとめ

振り返ってみると、一応はトークンでのアクセス出来ましたし、手順もそんなに多くはないです。
AD B2C の設定手順も Keycloak に比べてステップがすごい多いということはありません。
(Keycloakも慣れなければなかなか難解です。)

が、情報の少なさと不正確さと、UIの分かりにくさが半端ないので非常に手こずりました。。。
この手順も何ヶ月持つのかと思うと切なさと愛しさと心強さと。。。もう、愛のままにわがままに僕は君だけを傷つけない気持ちでこのまま君だけを奪い去りたいです。。。

脳みそが蕩けてますが・・・続いて Postmanではなく、Angularのクライアントからのアクセスに挑戦してみたいと思います。

今回作成したコードは以下のリポジトリにあげました。Quarkusの方はほとんどコーディングしてないけど・・・ご参考にどうぞ。

いや〜大変だった!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?