OpenID Connectについて知るときに読んだもの

OpenID Connect (OIDC)がどういうものか、どう使うのかについて知るために読んだものについてまとめておく。前提として、OAuth 2を利用したアプリケーションの開発経験はあるとします。

OAuth 2

OIDCはOAuth 2を拡張した認証プロトコルとなっている。前提に書いたとおりOAuth 2を使ったアプリケーション(認可サーバ、クライアント両方)の開発に携わった経験はなんどかあったものの、念のため復習した。

OAuth 2 in Action

OAuth 2をさらうために、網羅度が高そうな"OAuth 2 in Action"を読んだ。邦訳は『OAuth徹底入門 セキュアな認可システムを適用するための原則と実践』。

OAuth 2の各グラントタイプを、認可サーバ、クライアント、リソースサーバーそれぞれの役割と認可のフロー、それらを実現するコードで説明している。コードは基本的にJavaScriptで書かれていて、GitHubにもリポジトリ(oauthinaction/oauth-in-action-code)がある。さらに、認可サーバ、クライアント、リソースサーバそれぞれについて、よくある脆弱性とその対策を説明している。

記述がよくも悪くも冗長なので、コードやシーケンス図を読んで、わからないところがあれば本文で補完していくような読みかたがいいかもしれない。

OpenID Connect

OAuth 2 in Action

上で紹介した"OAuth 2 in Action"の後半で、OIDCについて1章割いて説明している*1。OAuthでは誰が認可したのかはクライアントからわかりえないので認証ではない、という話に始まり、他の章と同様に実装を交えてOIDCを説明している。

OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本

もう少し理解を整理するためにAuth屋さんが頒布している次の本も読んだ。

authya.booth.pm

この本のいいところは、いわゆる「OAuth認証」から始めて、OIDCがなぜ出てくるかまで次のような流れで説明しているところだと思う。

  • OAuthでは、クライアントからはユーザを認証(つまり使っているのが誰なのか判断することが)できない
    • アクセストークンはクライアントから見るとopaqueだから情報がない
  • プロフィールAPIがリソースサーバにあれば認証に近いことができる(これが「OAuth認証」)が、トークン置き換え攻撃やクライアントの脆弱性で乗っ取りが発生しうる
  • Facebookなどdebug_token APIを導入して、「OAuth認証」をよりセキュアにしているサービスもある
  • これらを踏まえて、OAuthを土台としつつセキュアな認証機能を標準化するのがOIDC

RFCなど

IDトークンの検証方法は『OAuth、OAuth認証、OpenID Connectの違いを〜』にも書いてあるが、OIDCのRFCで説明しているのでそちらも確認した。

Final: OpenID Connect Core 1.0 incorporating errata set 1 - 3.1.3.7. ID Token Validation

また、OAuth 2/OIDCの周辺技術に関して、いくつか文書を読んだ。

OIDCではBearer TokenをプロフィールAPIを含むAPIへのアクセス時のトークン送信で使い、JWTをidentity providerからIDトークンとして取得するので、それぞれ注意点やベストプラクティスをあらためて把握するためにRFCを読んだ。"OAuth Security Current Best Practice"は"OAuth 2 in Action"の脆弱性に関する部分と少し被るが、こちらも生まれうる脆弱性とその対策について広く説明している。

OAuth 2/OIDCのRFCは長大なので、ひとまずライブラリの実装と標準の仕様を比較するときなどに読む方針にしている。

OpenID Connectを実装したライブラリ

コードを読むと理解が深まりやすいので、なにか現実のライブラリを読むのはいいことだと思う。注意点は、あくまでもRFCなどが説明する仕様が正であって、実装はなんらかの差異や不足があると認識しておいたほうがよいこと。それも考慮してコードを読むとコントリビューションチャンスも生まれそう。

今回はdoorkeeper-openid_connectを使ってIDトークンの払い出しやDiscovery APIなど一通り実装し、relying partyとして認証サーバを叩いたりコードを読んで理解を深めた。

所感

はじめはなぜOIDCでIDトークンを検証したら認証したことになるのか理解しきれていなかったが、従来の「OAuth認証」で使うプロフィールAPIやdebug_token APIでやっていたようなことを、UserInfo APIと署名付きのIDトークンでやっていると考えると腑に落ちた。そういう意味では、『OAuth、OAuth認証、OpenID Connectの違いを〜』はそのあたりの観点を提供してくれたので大変助かった。とはいえ、それなりにいろいろ読んだものの、細かいパラメータや様々な拡張仕様など、わからないことはまだまだあるので、必要に応じて身につけていきたい。

*1:Chapter 13. User Authentication with OAuth 2.0

『決済サービスとキャッシュレス社会の本質』を読んだ

Eコマースに関するWebサービスを開発しているのもあって、たまに決済に関する本を読んでいて*1*2、今年は本屋に『決済サービスとキャッシュレス社会の本質』が平積みされているのを見つけたので、読むことにした。

決済サービスとキャッシュレス社会の本質 | きんざい

内容

決済事業者としてサービスを検討するときの実務や注意点を個別サービスに依存しない形で説明することで本質を導き出すとしている。

国際ブランド決済カード(いわゆるクレジットカード)のしくみに始まり、デビットカード、プリペイドカードやSUICAのような決済手段のしくみ、それに加えて今後の展望が書かれている。そのなかで、どうしてそのようなしくみになったのかについての歴史的/業界的な経緯や、新興系の決済サービスに不足している部分の指摘などが詳細に書かれている。

決済ビジネスは「超薄利多売の装置産業」であり、レアケースと思える不正利用がビジネスとしては致命的になるので、最初からそれらのケースを考慮して作っていく必要があると再三にわたって強調されている。また、国際的には、決済のインフラレイヤーについては国際標準にしたがって共通なしくみを各社が使うことでセキュリティ強化やコスト削減を進めつつ、それより上のサービスに関するレイヤーで競争や差別化を図っており、国内の民間企業もそうなっていくべきだと述べている。

著者としては、新興の決済サービス事業者の展開に対して思うところがあるようで、加盟店手数料の安さやポイント還元競争が安易に称賛される風潮に警鐘を鳴らしている。この理由としては、決済サービスで大規模な不正利用が起こったり、アクワイアラーのビジネスが立ち行かなくなることで加盟店への支払いが滞ると、金融機関などに連鎖的に影響が及ぶ「金融システミックリスク」が発生し、経済に影響を及ぼすことによる。従来のクレジットカード会社はセーフティネットとしての役割を果たすために金融システミックリスクを減らす企業努力をしており、そのコストとしてある程度の加盟店手数料を取っているのだとしている。安心安全を大前提として、事業者は責任を果たしつつキャッシュレスを進めることが重要であると述べられている。

所感

全体を通じて著者の知識と経験に基づいて書かれており、詳細かつ当事者でないと知らなさそうな経緯が細かく書かれているのは興味深かった。

決済事業に手がけるWebサービス事業者へのやり方(安価な手数料や高い割引率など特典ばらまき)に懐疑的な意見や、O社に関して業界の会合で関係者がした発言などが書かれてあり、実際にそれらの事業者に金融システミックリスクを考慮できていない点が一部あるのだろうとは思った。また、C社について政府系ファンドからの出資があったことについても懐疑的な意見が書かれていた。このあたりは出典があるような話ではないので、あくまでも著者の主張と捉えて読むのがよいと思った。書きっぷりについては、ある種の講演録のような感じがあり、同じ話がなんども出てくるのが若干冗長だった。

読書メモ

第1章 すべての決済サービスの基本といえる国際ブランド決済カードのしくみ

  • ブランド会社の大事な役割は、仮にアクワイアラー(加盟店管理会社)が倒産しても加盟店に報酬が渡るように補償して、金融システミックリスクを防ぐこと。拓銀倒産時に、その子会社をJCBが買ったエピソードなど。加盟店手数料はこういうところに使われている話から、決済を本業としない新興系サービスの安価な手数料について言及。新興系サービスの構造的に今後もつきまとう問題
  • ブランド会社がネットワークを流れる決済データの仕様やカードの仕様を決めている。IC未対応端末でのICカード不正利用の責任をアクワイアラに課すなど信頼性向上に努めている
  • FeliCaは近接型非接触ICカードに関してはISO仕様ではない。もともとSuicaで使われることを想定していたので、高速に動作する。役所が発行するようなカードにFeliCaを導入してしまうとWTOに訴えられる可能性があるので、マイナンバーカードなどはType-Bを採用している
  • 欧米は銀行を信用していないので、明細を見て自分で最低支払い金額以上の小切手を書いて、手数料を払ってでもリボ払いを使う。クレジットヒストリーを育てる意味合いもある。日本はカード会社を信用しているので、実質後払い一括デビットといえる
  • Eコマースでの不正利用対策としてCVVが使われるようになったが運用上問題が発生しがち。3Dセキュアは加盟店にカード情報を持たせないための方法

第2章 デビットカードと送金サービス

  • 米では口座開設に与信が必要なことや、リボ払い中毒の問題からデビットカードが普及しつつある
  • 日本はJ-Debitというのがあるが、あまり流行っていない。Bank PayがJ-Debitのインフラを利用している
  • デビットカードは特有の考慮事項がある
    • オーソリ時にはあった口座残高が売上確定時になくなるオーバードラフト。銀行側でオーソリ時点での金額分留保で対応することが増えている
    • オーソリ後の売上金額変更
    • オーソリと売上で電文が通るアクワイアラが変わるとき、イシュア側で突合が難しくなる。オーソリと売上を一つの電文にまとめて解決する(シングルメッセージ)。
  • 2016年のセブン銀行ATMからの18億円不正引き出し事件。セブン銀行は悪くなく、引き出しを許可した南アフリカの銀行に問題があった。セブン銀行ATMはIC対応しているし事件のときは最大引出額を下げる対応をとった。そもそも磁気カードは偽造しやすいので、ICにしていくことが大事
  • キャッシュアウトはデビットカードによって店頭で現金を引き出せるので、ATMがない地域で便利

第3章 プリペイドカードと電子マネー

  • 日本における電子マネー(Edy、Suica、nanaco、WAONなど)の発展の経緯、国内外でFeliCaの利用実績にズレがあることや、Apple PayがFeliCa対応してしまったことの功罪について、LINE Payカードなどブランドプリペイドカードについて

第4章 新たな決済サービスの動向とキャッシュレス社会の展望

  • 中国のキャッシュレス事情、新興系決済サービスやQRコード決済について、今後の展望など
  • 2010年代前半の爆買いは人民元の持ち出し制限と銀聯のデビットカード誕生によるもの。そのあとにアリペイやWeChatペイが追随した。単純にQR決済を入れればまた起こるというものではないし、中国当局の規制が入った

APIスロットリングの実現方法

ある期間でのあるWeb APIに対するアクセス回数上限を与えたときのスロットリングについて、一例ではあるがRack::Attack (v6.3.1)で採用されているしくみについて調べた。

ここでは、ある期間において特定のクライアントからのアクセスを一定回数以下に制限することをスロットリングと呼ぶことにする*1。あるAPIにスロットリングをかけるときは、ある期間において経過した時間を計算しながら、その期間中にあるクライアントからエンドポイントに来たアクセスの回数をサーバサイドのキャッシュに記録する必要がある。

結論

スロットリングしたい期間をperiod(単位は秒)とする。

periodで与えられる期間だけ有効なカウンタ」を作ることができれば、そのカウンタにアクセス回数を記録することで、あるクライアントから上限を超える回数のアクセスが来ているかどうか検証できる。つまりスロットリングが実現できる。

カウンタの実現方法

特定期間において経過した時間を計算する

剰余を使ってタイマーを作ることで実現する。

まず、アクセスが来た時点でのUNIX時間t*2を取得する。このとき、tperiodで割ったときの剰余rを計算すると、当然のことながら0 <= r < periodがつねに成り立つ。つまり、1秒ごとに数値が増え、period - 1の次は0に戻るようなタイマーができる。Rubyで書くと次のようになる。

r = t % period # 1秒ごとに増加するタイマー。値は0からperiod - 1を繰り返す

そして、periodからrの値を引くと、1秒ごとにperiodから数値が減り、1の次は値がperiodに戻るようなタイマーができる。

(period - r).to_i # 1秒ごとに減少するタイマー。値はperiodから1を繰り返す

アクセス回数の記録時に、このタイマーから得られる値ををキャッシュの有効期限として設定することで、その期間における残り時間を表現できる。

なお、Rack::Attackでは実装上の理由でタイマーの最大値に1秒のバッファを持たせている*3

特定期間のアクセス回数を記録する

除算を使って、特定の期間だけ得られるキーを作り、そのキーを使ってキャッシュにアクセス回数を記録する。

先ほど取得したUNIX時間tperiodで割ったときの商qを仮に毎秒計算してみると、剰余が0 <= r < periodになる連続した範囲ごとにqの値が同じになる。

q = (t / period).to_i # periodの期間中はつねに同じ値

つまり、ある期間の間はqの値が同じになるので、アクセス元の情報とqの組み合わせをその期間のクライアントからのアクセス回数のキーとすると、あるクライアンのある期間におけるアクセス回数データを一意に特定できる。このキャッシュの有効期限を、先ほど計算した有効期限として設定すればよい。

以上より、periodで与えられる期間だけ有効なカウンタを作ることができる。このカウンタを利用してスロットリングを実現する。

参考

  • Rack::Attack 6.3.1
    • Rack::Attack::Throttle
      • matched_by?から呼び出しているcache.countが「periodで与えられる期間だけ有効なカウンタ」での計数を実現している
    • Rack::Attack::Cache
      • key_and_expirtyで時刻に基づいて経過時間とキャッシュのキーを計算し、do_countでカウンタを操作している

*1:Rack::Attackではthrottlingと呼んでいる

*2:RubyではTIme.now.to_iで取得する

*3:https://github.com/rack/rack-attack/pull/85