Django+ReactNativeでCookieでセッション管理
今のサービスClubCloudをネイティブアプリ化するため、認証をいじっています。
Djangoのデフォルトは、Cookieを使ったセッション管理と認証です。
これを、Tokenベースのステートレスな認証に変えようかと思いましたが、とりあえず現状のCookieベースでネイティブアプリと連携させることにしました。
Djangoは全然RESTじゃない?
最近だとTokenを使ったRESTfulな実装が一般的かなと思います。
RESTとは以下のような設計原則です(これに従うのが、RESTfulと言われる)。
1. セッションなどの状態管理を行わない。(やり取りされる情報はそれ自体で完結して解釈することができる)
2. 情報を操作する命令の体系が予め定義・共有されている。(HTTPのGETやPOSTメソッドなど)
3. すべての情報は汎用的な構文で一意に識別される。(URLやURIなど)
4. 情報の内部に、別の情報や(その情報の別の)状態へのリンクを含めることができる。
かいつまむと、URLがリソース(ユーザー情報とか)で決まって、処理がHTTPメソッドで決まる、ステートレスなAPIです。
以下はRESTじゃないAPIとRESTfulAPIの違い(の一部)の例です。
Django
URL : https://sample.com/user_update
処理 : URLですでに処理の分岐、もしくは内部で何らかのパラメタで分岐させてユーザー情報のCRUDを行う
RESTfulAPI
URL : https://sample.com/user_info/
処理 : HTTPメソッドで一意に決まる(PUTなら更新とか)
Djangoはデフォルトではセッション管理していてステートレスではないので、そのままではRESTfulではありません。
また、URLも特にリソースで決めてるわけでも、処理をメソッドで分岐させてもいない場合は、RESTfulじゃないです。
RESTのメリットデメリットは先ほどの出典の記事などを参照されたいです。
まぁRESTfulにするのはバックエンドのコードをかなり変える必要があるので、もっとずっと後でいいかなと。
あーでもDjango REST Framework使うとしたら、結局コード設計も変えにゃあかんのかな。。
URLルーティングは今のままがいいな…。
ログインor登録時のセッションIDの取得
てことで、セッション管理は続投なわけですが、今まではブラウザとサーバーのHTTPリクエスト/レスポンスのやり取りだったので、Cookieのやり取りは勝手にDjangoとブラウザがやってくれました。
Djangoのデフォルトの会員登録とログイン機能で十分でした。
が、アプリとなると、JSONでやり取りするだけなので、アプリ側で自分でCookie(セッションID)を毎回リクエストに入れてあげないとDjangoで認証できません。
一回セッションIDをアプリ内に保存してしまえば、あとはリクエストに入れるだけで問題ないのですが、最初にアプリ内に保存するとき、つまりセッションID発行時(ログインor登録時)に、保存するためにDjangoからJSONでセッションIDが欲しくなるわけです。
ふだんはHTTPレスポンスのCookieに入っているので、明示的にDjango側でJSONResponseの中に入れる必要があります。
またまたミドルウェア
やりたいことは、ログインor登録時に返却されるレスポンスをJSONResponseに変換して、Cookieに含まれるセッションIDをそのJSONResponseの中に含める、です。
前の記事で、Djangoのテンプレートエンジン利用時と、APIのときでレスポンスを出し分けるときにミドルウェアを使いました。
今回もミドルウェアを使います。
結果から言うと、ミドルウェアの一番上に、CookieからセッションIDを抜いて、JSONResponseに入れるミドルウェアを追加します。
def __call__(self, request):
response = self.get_response(request)
cookie = {}
for key in response.cookies.keys():
cookie.update({key:response.cookies[key].value})
return JsonResponse(cookie)
これは、CookieだけJSONにして返すミドルウェアのコードです。
response.cookieの中にCookieが入っているので、それらを辞書にまとめ直して、JsonResponseに入れています。
Cookieの値はresponse.cookies[key].valueで取り出しています。
.valueをつけないと、有効期限とか色々出力してしまいます。
参考:http.cookies --- HTTPの状態管理
Cookie以外の中身もJsonResponseに入れたい場合は以下の前の記事をご参照ください。
参考:Django、カスタムヘッダー + ミドルウェアでテンプレートとAPI用レスポンス(Json)
settings.pyに追加されているミドルウェアの実行の順番ですが、上記コードにもあった、
response = self.get_response(request)
よりも上のコード(つまり、レスポンス取得前)は、settings.pyのMIDDLEWARE = のリストの上から順番に、このコードよりも下のコード(レスポンス取得後)は、下から順番になってます。
今回は、デフォルトで追加されているSessionMiddlewareがレスポンスにセッションIDを追加したあとで処理を行いたいので、SessionMiddlewareよりも上に追加します。
最後の最後にする処理なので、一番上でも良いと思います。
このミドルウェアによって、アプリ側で受け取るJSONの中にcookiesという辞書が追加され、その中にsessionidという名前でセッションIDが入っています。
なぜミドルウェアで抜き出すのか、他に方法はないのか
たぶん、これ以外に方法はないように思います。
今回結構SessionMiddlewareとかログイン、デフォルトのSessionのモデルを調べたのですが、
・Sessionモデルでは、encodeしてユーザーのpkを保存しているので、このユーザーのセッションIDくださいっていっても検索できない(おそらく)
・ソースコードのどこかでセッション保存しているので、ソースコードを書き換えないと保存するセッションIDを盗み見できない(おそらく)
・同一ユーザーでもセッションIDは状況によって変わりうるので、何らかのランダム処理が入っている。そのため、自作の別の関数で、セッションIDを推測することは不可能(おそらく)
ってことで、ユーザーに渡す形でセッションIDが表に出てくるまではあまり手出しできなさそうな状況だったので、自作ミドルウェアで最後に抜き出すことをしました。