Upgrade to Pro — share decks privately, control downloads, hide ads and more …

クロスオリジンでログイン状態を継続させるように見せるテクニック

おdd
March 02, 2018

 クロスオリジンでログイン状態を継続させるように見せるテクニック

in YAPC::Okinawa(2018) 前夜祭LT

おdd

March 02, 2018
Tweet

More Decks by おdd

Other Decks in Programming

Transcript

  1. 自己紹介 氏名生年月日ハンドル 武藤 尚 Muto Nao (1996/06/25) Twitter 等 :

    @oddmutou CPAN: MUTO 所属等 シーサー株式会社 社員 ( したらば掲示板, SesaaWiki , BoomAppGames , etc.) 東京デザインテクノロジーセンター専門学校 非常勤講師 東京通信大学 学生 ( 春から )( 第一期生予定 )( ネタ入学で差をつけろ )
  2. とある新規サービス 仕様策定・設計等 他社競合 ? を見ると表示側はサブドメインを沢山つくってるしうちもそうしよう → 「そのほうがかっこよさそう ! 」( SEO

    的にはどうなんかよくしらんけど) dev.to 流行ってるし CDN で爆速配信!目標の月間 ? 億 PV に耐えれるインフラも ! → 「やるぞ ! 」 (dev.to が話題になるあたりに始めて年内リリース済みの爆速開発 ) (インフラ設計も俺) SNS 連携 / 会員登録的な機能も作ろう → 「テンション上がってきた ! 」「まぢ卍」
  3. 雑な構成図 なんとか .sample.com user.sample.com 新規登録 / ログイン / ログアウト アカウント設定

    アカウントへの通知なども - A.sample.com - B.sample.com - C.sample.com … 表示がメイン 裏側で使うようの何か .sample.com DB (記事データ等)や KVS (セッション等) ◦ 各 にはそれぞれ別のアプリが (マイクロサービス化的なことやってみたかったんです)
  4. →JS で取れるようにユーザー情報管理サーバーにログイン状態取得 API を作る →REST API? そんなのしらないヨ ということでセッションクッキーを見ちゃう $.ajax({ url:

    ‘https://user.sample.com’, type: 'GET', dataType: 'json', xhrFields: { withCredentials: true } }).done(function(response){ 以下略 「 xhrFields: {withCredentials: true} 」で cookie 送ってくれるのかーなるほどー
  5. 複数ドメイン間 (A.example.com → user.example.com) で API 叩くには Access- Control-Allow-Origin が必要というのはわかっていた。

    また,今回はログイン状態を取るだけであるため,厳しく設定する必要もないので,「 * 」に する。 $self->r->header_out('Access-Control-Allow-Origin' => ‘*’);
  6. > クロスオリジン要求をブロックしました : 同一生成元ポリシーによ り、 https://user.sample.com にあるリモートリソースの読み込みは拒否されます ( 理由 :

    CORS ‘ ヘッダー Access-Control-Allow-Credentials’ ‘ は true’ である べき ) 。 ドメインが異なる API のときは「 Access-Control-Allow-Origin: * 」ってつけてれば良い ことは知っていたが, Cookie 送るときはまた別のものが要るらしい。 $self->r->header_out('Access-Control-Allow-Origin' => ‘*’); $self->r->header_out('Access-Control-Allow-Credentials' => "true");
  7. > クロスオリジン要求をブロックしました : 同一生成元ポリシーにより https://user.example.com にあるリモートリソースの読み込みは拒否されます ( 理 由 :

    CORS ‘ ヘッダー Access-Control-Allow-Origin’ ‘ が *’ である場合、認証情 報はサポートされない ) 。 Cookie 送るときは「 Access-Control-Allow-Origin: * 」だと怒られるらしいので,もう無 … 理やり 。 とりあえずこれで取得できて,超いい感じに表示できた。 $self->r->header_out('Access-Control-Allow-Origin' => $self->r->header_in('Origin')); $self->r->header_out('Access-Control-Allow-Credentials' => "true");
  8. 毎回 API 叩くのは負担になるからブラウザ側でキャッシュしたい ! →sessionStorage に保管する →sessionStorage は KVS なので出し入れ時に

    JSON にデコード / エンコードする。 → 普段 SSR なサービスしか触ってないと知らないことだったけど,かんたん。 sessionStorage.setItem('login_status', JSON.stringify(response)); JSON.parse(sessionStorage.getItem('login_status')));
  9. キャッシュクリアのタイミングどうする ? → sessionStorage なのでウィンドウ閉じればクリアされるが,それ以外に も, user.example.com でのログイン時・ログアウト時にキャッシュをクリアする必要があ る。 →

    cookie のように「 *.example.com 」のようにスコープにワイルドカードを使いたいが, sessionStorage のスコープは「発生元のオリジン」 ( スキーム & ドメイン & ポート ) で固 定されており, user.example.com から A.example.com の sessionStorage を操作す ることはできない。
  10. 革命的発案 *.example.com な cookie に「ログイン状態の最終更新日時」を保管し,それを読んで sessionStorage を操作する &API を叩いて更新すればよい user.example.com

    ログイン,ログアウト時に cookie を更新 $.cookie('update_time', 今の時間 , {path: '/', domain: '*.example.com'}); A.example.com ログイン,ログアウト時に cookie を更新 last_time = localStorage.getItem(‘last_time’;) time = $.cookie('update_time'); if (time > last_time) { localStorage のキャッシュを使う } else { API 叩く &localStorage のキャッシュを更新 localStorage.setItem(‘last_time’, 今の時間 ); } ↑ 「時間」の中身は端折りましたが,私は unix 秒 ( 整数値 ) を使いました。また jquery.cookie.js を使っていま
  11. 全く別ドメインのサービスでもユーザー情報使いたい ! → 敗北宣言 :iframe 使います user.example.com ログイン・ログアウトページ iframe 内の

    hogehoge.com $.cookie('update_time', 今の時間 , {path: '/', domain: 'hogehoge.com'}); hogehoge.com 先ほどの A.example.com と同様に, cookie の update_time を見て localStorage のキャッシュを使うか, API 叩いて更新するかします。
  12. ユーザーで独自ドメインが設定できるサービス A ↓ 記事ページにもログインしてコメントのフォームが欲しい ! ユーザーの独自ドメイン 適用ページ - トップページ -

    記事ページ 等表示だけのページ & サーバー側でキャッシュも効いている 独自ドメイン 非適用ページ - コメントページ 等ログインして書き込みとかできるページ
  13. サービス A において,静的ページでもログイン状態を表示したい場合は,セッション用の cookie が存在すれば API を都度叩く,という実装が既にあった。 ( 想定アクセス数的にキャッシュするほどでもないが,クローラーや一見さんの閲覧時に 無駄に

    API 叩かれるのは怖いため。 ) ↓ 独自ドメインの場合はセッションが無いので,別途ログインするユーザーか見分ける印を 付ける必要がある。 そして歴史あるサービスであるため,深く弄りだすと年が暮れるので避けたい。
  14. 〜サービス A のログイン時の挙動〜 独自ドメインページ(ユーザーのドメイン)の「ログイン」リンクをクリック ↓ サービス A のデフォルトドメイン(弊社のドメイン) ↓ リダイレクト

    弊社の複数サービスで使っているアカウントサーバー ↓ リダイレクト サービス A のデフォルトドメイン ↓ リダイレクト 独自ドメイン(「ログイン」リンクがあったページに戻る)
  15. 〜サービス A のログイン時の挙動〜 独自ドメインページ(ユーザーのドメイン)の「ログイン」リンクをクリック ↓ サービス A のデフォルトドメイン(弊社のドメイン) ↓ リダイレクト

    弊社の複数サービスで使っているアカウントサーバー ↓ リダイレクト サービス A のデフォルトドメイン ↓ リダイレクト 独自ドメイン(「ログイン」リンクがあったページに戻る) ここにログインしていることを示す SessionStorage 保管する処理を追加
  16. 〜サービス A のログイン時の挙動〜 独自ドメインページ(ユーザーのドメイン)の「ログイン」リンクをクリック ↓ サービス A のデフォルトドメイン(弊社のドメイン) ↓ リダイレクト

    弊社の複数サービスで使っているアカウントサーバー ↓ リダイレクト サービス A のデフォルトドメイン ↓ リダイレクト 独自ドメインで Cookie を操作 ↓ リダイレクト 独自ドメイン(「ログイン」リンクがあったページに戻る)
  17. 〜サービス A のログイン時の挙動〜 独自ドメインページ(ユーザーのドメイン)の「ログイン」リンクをクリック ↓ サービス A のデフォルトドメイン(弊社のドメイン) ↓ リダイレクト

    弊社の複数サービスで使っているアカウントサーバー ↓ リダイレクト サービス A のデフォルトドメイン ↓ リダイレクト 独自ドメインで Cookie を操作 ↓ リダイレクト 独自ドメイン(「ログイン」リンクがあったページに戻る)