はじめに

西日本テクノロジー&イノベーション室の山田です。ここ1年ほどはコロナの影響によってほとんどリモートで働いています。

現在所属しているプロジェクトにおいて、認可設定の確認を効率的に行えるようWeb APIの設計・実装をすることで品質を担保しました。 どのようにWeb APIの認可設定の確認を効率的に行えるようにしたのか、またそのやり方のメリットについてお話します。

プロジェクトの概要

私たちが開発しているのは、企業がイベントやプロジェクトの参加者を募集するためのマッチングサイトです。今回のアプリケーションを開発するにあたって、もっとも重要なことの1つに「正しく認可制御されること」がありました。「正しく認可制御されること」を満たすためには、認可の設計・設定・確認それぞれを正しく行う必要があります。本プロジェクトでは認可設計は別途担当者が行ったため、エンジニアは設計通り正しく認可設定し、確認する必要がありました。

見えてはいけない情報が権限のないユーザに閲覧/編集されてしまうことはセキュリティ上の問題はもちろん、アプリケーションを開発した自社の信用にもかかわるため、いずれも重要な作業です。

今回のアプリケーションにはロールが8種類あり、ロールによって許可されている動作が異なるという要件がありました。また、認可設定の確認者は認可まわりの業務仕様には精通していますがエンジニアではないため、ソースコードを直接レビューしてもらうことはできない状況でした。

エンドポイント設計の課題

まず初めに、一般的に示されるREST APIのエンドポイントの設計方法に則って設計を行いました。主に以下の要素に気をつけました。

  • エンドポイントにはリソースを表現する名詞を使用する
  • 動作に適したメソッドを使用する

たとえばGETメソッドで/usersにアクセスするとユーザ一覧を取得できる、というように設計を行いました。エンドポイントにアクセスできるロールの制約がある場合は、ビジネスロジックの中にロールのチェックを行う分岐を追加し、認可制御を行いました。

開発を進めると「認可の設定が正しくできているかの確認が難しい」という課題があることに気づきました。課題の原因には以下の要素があると考えました。

  • ロジックに認可制御を行うための分岐が入る
  • エンドポイントを見ただけでは各ロールのふるまいが分からない

上記の理由から、この時点のエンドポイント設計では認可の妥当性を確認するために入念なコードレビューを行う必要がありました。しかし、エンジニアでない認可設定の確認者にコードレビューは依頼できません。 仮にレビューが行えたとしても、ロールが8種類、エンドポイント数も150程度あるので間違いなく全量をコードレビューすることも難しい状況でした。

エンドポイント設計で工夫したこと

そこで、エンドポイントの設計方法にルールを追加してエンドポイントを修正することにしました。

「ミスなく正しく認可設定できること」を満たすために以下の2つの工夫を行いました。

  1. 各エンドポイントに対して許可されるロールがわかるよう設計を行う。
  2. 認可設定をファイルに記述し、アプリケーションに反映することで認可制御を行う。

それぞれに関して詳しく説明します。

1. 各エンドポイントに対して許可されるロールがわかるよう設計を行う

例に示しながら説明します。 以下の要件を満たしたい場合を考えます。

  • ロールはグループA一般、グループA管理者、グループB一般、グループB管理者、システム管理者の5つ
  • ユーザ一覧を取得したい。ただし、ロールによって取得できる情報に違いがある
    • 一般ユーザは自身の情報のみ取得できる
    • グループA管理者、グループB管理者は自身と同じグループのユーザ一覧を取得できる
    • システム管理者ユーザは全ユーザの一覧を取得できる

各ユーザが取得できる情報を改めて以下に示します。

取得できる情報 グループA一般 グループA管理者 グループB一般 グループB管理者 システム管理者
自分自身のみ
同一グループユーザの一覧のみ
全ユーザの一覧

上記のロジックを認可を意識しないエンドポイント設計で実現すると以下のようなイメージになります。

  • エンドポイント:GET /users
  • ユーザごとの分岐処理
      Response getUsers(UserContext loginUser) {
          // グループA一般、グループB一般ユーザの場合
          if (loginUser.isNormalUser()) {
              return getUser(loginUser.userId);
    
          // グループA管理者、グループB管理者ユーザの場合
          } else if (loginUser.isGroupAdmin()) {
              return getUsersByGroup(loginUser.group);
    
          // システム管理者ユーザの場合
          } else if (loginUser.isSystemAdmin()) {
              return getAllUsers();
          } else {
              throw new IllegalArgumentException();
          }
      }

    このように設計した場合、エンドポイントを見ただけでは各ロールのユーザがどのような情報を取得できるのかわかりません。その確認をするためには、上記のソースコードを確認する必要があります。

一方、「各エンドポイントに対して許可されるロールがわかるよう設計する」というルールに沿ってエンドポイント設計を行った場合は、以下の3つに分かれます。

エンドポイント エンドポイントで行うこと グループA一般 グループルA管理者 グループB一般 グループB管理者 システム管理者
GET /users/me 自分の情報のみ取得する
GET /groupAdmin/users 同じグループのユーザ一覧を取得する
GET /systemAdmin/users 全ユーザの一覧を取得する

このように設計した場合は、ロールによる分岐が存在していてもそれぞれに許可されている動作が一目でわかります。ロジック内の分岐もなくなり、ミスしづらい状況になったと考えられます。

各エンドポイントに対して許可されるロールがわかる設計方法のメリットとデメリット

この設計方法にはメリットとデメリットがあります。以下にそれぞれを示します。

  • メリット
    • 各エンドポイントに対して許可されるロールがわかりやすい
    • ロールに関連する認可のロジックが業務ロジックに紛れ込まない
  • デメリット
    • エンドポイント数が増える
    • API利用時にロールによってエンドポイントを選択する必要がある

本プロジェクトでは複雑な認可設定を誤りなく実装する必要がありました。
そのため、とくに「各エンドポイントに対して許可されるロールがわかりやすい」ことによって、認可設定を誤ったときにもバグを発見しやすい点が大きなメリットになると考えました。

デメリットの「エンドポイント数が増える」ことに関しては、エンドポイント数を無理に減らしても、そのエンドポイント内で分岐が発生してしまうことを考えると問題ではないと考えました。「API利用時にロールによってエンドポイントを選択する必要がある」ことに関しては、確実に認可設定できることによる情報漏洩の防止ができるという利点が勝ると考え、今回の設計方法を選択しました。

2. 認可設定をファイルに記述し、アプリケーションに反映することで認可制御を行う

次に、認可設定をファイルに記述し、アプリケーションに反映することで認可制御を行うことにしました。この方法には以下のメリットがあると考えました。

  • 設定が一覧できる
    • 今回のアプリケーションではエンドポイントが約150と多かったため、設定が一か所に集まっていることで確認・修正の手間を少なくできる
  • ロジック内部にロール関連の認可制御を記述する必要がなくなり、確認が容易である
  • ソースコードを読めない人でも理解できる

さらに、アプリケーション起動時に設定ファイルとエンドポイントをチェックし、設定に乖離がある場合はエラーになるよう設定しました。この対策によって、エンドポイントは存在するのに認可設定が存在しないといった事象が起こることを防ぎました。

設定ファイルの形式には、認可設定の確認者に確認してもらいやすいようCSVを選択しました。1つのファイルに設定を集約することによって並行して編集された際に競合は起きますが、CSVであったため統合は容易でした。

認可設定の確認

アプリケーションの開発がひととおり完了した後、認可設定の確認者に認可設定を確認してもらいました。 設定ファイルを参照し、各エンドポイントで行っていることを説明しながら誤りがないか確認するという方法で行いました。具体的には以下のような認可設定を記述したCSVファイルを参照しつつ、使用される状況や画面の説明を口頭で追加して確認作業を行いました。

実際に確認作業を行うと設定を誤っている箇所があったり、仕様に出しきれていなかった設定があったりすることが分かりました。慎重に確認することで事前に問題を防ぐことができたと考えています。 認可設定の確認者に確認してもらった設定がそのままアプリケーションに反映されるため、修正の確度も高いと考えています。

冒頭でお伝えしたとおり本プロジェクトにとって「正しく認可設定されること」は重要な要件であったため、このように力を入れて確認作業を行いました。

おわりに

認可設定の確認を効率的に行うことで品質を確保するためのWeb API開発手法の紹介を行ってきました。

各エンドポイントに対して許可されるロールがわかるよう設計することで、エンドポイントとロールの対応付けが分かりやすくなる反面、エンドポイント数は多くなります。今回の方法にはメリット・デメリットがあるため、適用するアプリケーションの特徴を踏まえて判断する必要があります。

エンドポイントに対して許可されるロールがわかるよう設計し、それを一覧化することで、今回のプロジェクトにおいては重要視されていた箇所を重点的に確認でき、十分メリットがあったと考えています。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。