はじめに
本ブログでは、Spring Bootについて私が1から調べながら学んだことを記事にしています。このシリーズでは、それらの知識を前提に記載していきますのでご了承ください。
さて、今回からSpring Boot + Spring Securityを用いて認証・認可ありREST APIの作成について記載していきます。これについては私もわからないことが多く、かなり四苦八苦しています。内容に誤りがある場合などはTwitterのDMなどで教えていただけると幸いです。
そもそもSpring BootでREST APIを作成するメリットがあるかといわれると私にはよくわかりません。Ruby on RailsやLaravelといったフレームワークの方が楽そうなイメージです。が、一通り触れておきたいのでやります!!
この記事ではイントロダクションとして、実装の前に必要な知識について説明をしていきます。
REST API
REST APIは、REST(REpresentational State Transfer)と呼ばれる設計モデルを基に作られたAPIのことです。
REST 設計原則
RESTにはいくつか設計原則があり、これに準拠したものをRESTfulと呼ばれるようです。ここではWikipediaの内容を参考に、個人的な解釈を記載していきます。
リソースを一意に識別する「汎用的な構文」
RESTでは、リソースへのアクセスにアドレス(URI)を使用します。APIとして利用する場合は、名詞で命名するようです。
すべての情報(リソース)に適用できる「よく定義された操作」のセット
リソースへのアクセスには、目的に応じたHTTPメソッドを利用します。またそれらの用途が統一されるようにします。
主に以下の4つが使われているイメージです。
- POST:Create
- GET:Read
- PUT:Update
- DELETE:Delete
ステートレスなクライアント/サーバープロトコル
通常のクラサバではセッション管理などがありますが、RESTではセッションは使いません。ワンプロセス(1回のリソースへのアクセス)に必要な情報をすべて含めます。
アプリケーションの情報と状態遷移の両方を扱うことができる「ハイパーメディアの使用」
通常のハイパーテキストに加え、ハイパーメディアも使えるよーということだと思います。自信はない…。
ハイパーメディアは、ハイパーテキストに画像、音声、動画、ハイパーリンクなどを加えたものみたいです。
結局のところJSONがよく使われているイメージです。
HTTPステータスコード
REST APIではリソースへのアクセス結果をHTTPステータスコードとして返します。クライアント側は、HTTPステータスコードを基に処理のハンドリングを行います。
以下に代表的なものをいくつか記載しておきます。
コード | メッセージ | 説明 |
200 | OK | 成功 |
201 | Created | 作成成功(POST) |
204 | No Content | 成功だが、返すリソースはなし |
400 | Bad Request | 汎用エラー |
401 | Unauthorized | 認証エラー |
403 | Forbidden | 権限エラー |
404 | Not Found | 該当リソース無し |
405 | Method Not Allowed | メソッド未対応 |
409 | Conflict | リソースなどの衝突(作成時など) |
500 | Internal Server Error | サーバーエラー |
CSRF対策について
CSRF(クロスサイトリクエストフォージェリー)は、WEBアプリケーションの脆弱性、またはそれを利用した攻撃方法の1つです。詳細は割愛しますが、攻撃用サイトからのリクエストを受け付けてしまい、意図しない処理が実行されたりします。
CSRFの問題は、無関係のサイトからのリクエストを受け付けてしまうところにあります。この対策として有効なのがワンタイムトークンになります。以下は処理のイメージです。
ワンタイムトークンは自身のサイトでのみ認識できるので、他サイトからのリクエストをブロックすることができます。
Spring Securityでは、デフォルトでCSRFの対策が行われています。が、REST APIの場合はいくつか問題があります。
1つ目はセッションについてです。RESTはステートレスのためセッションを利用しないのが基本です。そのためトークンの保存先をセッションとは別の場所に変更する必要があります。
2つ目はクライアント側の実装です。APIの場合、クライアント側は意図的にトークン要求をしない限りはトークンを取得することができません。つまり、本来のリクエストに加え、トークン取得用のリクエストを送信するという手間が発生します。
これらを対応しようとするとやはりめんどくさいというのが正直な感想です。
そこでCORSという話が出てきます。
CORS(Cross-Origin Resource Sharing)
オリジン(Origin)とは
本題に入る前にオリジンについて説明しておきます。
オリジンとは、プロトコル、ホスト、ポート番号のことです。
例)http://localhost:8080/api/sample
- プロトコル:http
- ホスト:localhost
- ポート:8080
同一オリジンとは、3つすべてが同じであることを意味します。逆に1つでも異なる場合は、別のオリジンとなります。
CORSとは
CORSは、異なるオリジン間のリソースに対する制限のことです。基本的に、同一オリジン以外からのアクセスはすべてブロックするようにします。許可する場合は、ホワイトリスト形式で対象のオリジンを設定します。
オリジンを許可したとしてもすべてのリソースにアクセスできるとは限りません。実はHTTPメソッドやHTTPリクエストヘッダーにも制限があります。これらを許可しない限りは、異なるオリジンからのアクセスはブロックされます。
ここでCSRFの話に戻すと、CORSで異なるオリジン(サイト)からのアクセスはブロックできるからわざわざトークン使わなくてもいいよね?つまり、CORSをちゃんと設定しようということになります。
プリフライトリクエスト
※ これに関してはちょっと自信なし。
プリフライトリクエストは、上記のCORSの制限に関する情報を取得するためのものです。これはブラウザが自動で送信してくれるみたいで、返ってきたHTTPレスポンスヘッダーの内容から本当にリクエストをしていいのか判断します。
つまり、サーバー側はこのプリフライトリクエストに対し、CORSの制限に関する情報をレスポンスヘッダーに付与して返す必要があります。
といってもこの実装はフレームワーク側でなんとかしてくれていることが多いみたいです。
一部ですが設定するレスポンスヘッダーについて記載しておきます。
Access-Control-Allow-Origin
アクセスを許可するオリジンを設定します。すべて許可する場合は*(アスタリスク)を設定します。
Access-Control-Allow-Origin: https://ex2.com
Access-Control-Allow-Methods
アクセスを許可するHTTPメソッドを設定します。デフォルトではGET、POSTが許可されているようです。すべて許可する場合は*を設定します。
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers
アクセスを許可するHTTPリクエストヘッダーを設定します。すべて許可する場合は*を設定します。
Access-Control-Allow-Headers: Authorization
認証・認可について
通常のクラサバでは、認証したユーザーの情報はセッションに登録します。が、REST APIではセッションを使用しないためそうはいきません。
方法としてはいくつかあるみたい?ですが、ここでは認証トークンを使用します。
認証トークンを用いた認証・認可について、大まかな流れを説明しておきます。
【認証】
- クライアント側は、認証に必要な情報(username、password)を認証用のAPIに送信します。
- サーバー側は、送られた認証情報を基に認証処理を行い、認証に成功した場合はユーザー情報などを埋め込んだトークンを作成し、クライアント側に返します。
- クライアント側は、受信したトークンをローカルストレージに保存します。
【認可】
- クライアント側は、ローカルストレージに保存しているトークンをリクエストヘッダーに設定し、APIに送信します。
- サーバー側は、リクエストヘッダーからトークンを取得し、さらにトークンからユーザー情報を取得することで認可処理を行います。認可に成功した場合は、APIの結果を返します。
では少し補足していきます。
認証トークンの作成
認証トークンの作成には、JWT(JSON Web Token)を使用します。まずはこちらのサイトを見てもらうのがいいかもしれません。

サイト右側のDecodedにあるように、JWTはHeader、Payload、Signatureの3つの要素を持ちます。
Headerには、Signatureのハッシュ化アルゴリズムやトークンのタイプを指定します。
Payloadには、共有したい内容を設定します。今回の場合はusernameなど必要な情報を設定します。
Signatureには、正しいトークンであるかを判定する役割があります。HeaderとPayloadをBase64でエンコードしたものをさらに指定したアルゴリズムでハッシュ化したものになります。ハッシュ化にはSecret Key を使用します。Secret Keyはサーバー側でのみ認識できるものでなくてはいけません。
サイト左側のEncodedの結果が認証トークンとなります。認可の際にはこれをデコードして、Payloadからユーザー情報を取得します。
認証トークンの送信
認可が必要なAPIにアクセスする場合、クライアント側は認証トークンをリクエストヘッダーに設定して送信します。どのように設定するのかというと、実は認可用にAuthorizationというヘッダーが存在します。
以降MDNの内容を参考に記載します。
Authorization: <type> <credentials>
<type>には認証の種類を設定し、<credentials>には認証情報を設定します。
よく使われる?のはBasic認証で、usernameとpasswordをコロンで結合した文字列を、Base64でエンコードしたものを<credentials>に設定して送信します。
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
しかし、今回送信したいのは認証トークンです。このようなトークンを使用する場合にはBearer認証を使用します。BearerはOAuth2.0の仕様ですが、このようなトークンを送信する際も使用しても良いみたいです。
実際には次のようにして送信します。
Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.q9OTb6xt-EKqDDISQarkPU_BH-1OXpWaiGAVbvx5Hy9W3XPV_2S9S8s_X14lFHcdmr9KbL034xuzaCAzIHhZQw
あとがき
今回はイントロダクションとして、次回以降の実装に向けて必要な知識についてザっと説明していきました。
正直CORSや認証・認可については理解するのに一苦労しました…。といってもちゃんと理解できているかは正直なところ自身がないですね…。
次回以降はSpring Boot + Spring Securityで認証・認可ありのREST APIの作成について説明していきます!
- Spring Bootのおすすめ書籍はコチラ -
コメント