金曜日, 12月 05, 2014

IsomorphicとAPIファーストについて #apijp このエントリーを含むはてなブックマーク


Web API Advent Calendar 2014 の5日目の記事です。」

最近、Isomorphicという言葉をよく聞く。
Isomorphicとは、サーバーとクライアントのコードを同時に記述するパラダイムのことだ。
つまり、フロントエンド・バックエンドを曖昧にするようなアーキテクチャである。

私は、IsomorphicはWebのアーキテクチャーに合っていないと思っていて、やはりAPIファーストで設計し、フロントエンド・バックエンドをきちんと分けるべきだと考えている。

この点について詳しく述べたいと思う。

Isomorphicな開発環境が求められる背景


Isomorphicな開発環境が求められる理由としては、こちらのMeteorの記事が大変わかりやすい。(リアルタイムWebアプリケーションフレームワークMeteorについて)
  • 普通のWebでは状態が画面遷移でリセットされてしまうことが辛い
  • バリデーションをフロントエンド・バックエンドの両方で実装しなければならない
  • フロントエンドだけのスキルでは不十分。バックエンドの実装が特に面倒くさい。
  • フロントエンド・バックエンドを別々に勉強するのではなく、同時に作っていくイメージで開発したい
つまりは、

フロントエンドでも簡単に永続化されたオブジェクトにアクセスできるような仕組みのリアルタイムWeb開発フレームワークが欲しいということだと思う。Meteorであれば実現できるらしい。

例えば、meteorはフロントエンドでもバックエンドでもない、"Isomorphic" なフレームワークであり、WebSocket を利用しつつ pub/sub モデルで抽象化したような形で同期を取る。

Isomorphicなフレームワークの課題


2012年に登場して脚光を浴びたMeteorだが、それ以降は大きな広がりは見せていない。
Meteorは果たして失敗したのか。であるならその原因は何だったのか。

個人的に思いつくものを挙げてみる。

1. 信頼性の低いWeb環境を甘くみたこと

いつもいっていることだが、「分散システムでは信頼を失う」という真理に目を背けてはいけない。

リモートでは読込に時間がかかったり、更新に失敗したり、失敗したかのように見えて実は成功していたりということを考慮しないといけないはずなのに、ローカルと同様のシンプルな手続きで実行できるかのようにして本当に大丈夫なのか。

古くはJavaRMI、IIOP、RIAなど、誰もが挑戦して失敗を繰り返してきた歴史がある。これら死屍累々の歴史から学ぶべきことは多いように思うがなぜ生かされないのか。

2. 密結合になりがちなこと

ローカルにあるオブジェクトを更新するかのようにリモートのオブジェクトの同期を取ったり、逆にリモートのオブジェクトの更新があればローカルにも更新を伝達するような仕組みは、リアクティブかつ非同期に実行すればできそうに思える。(もちろん、正しく同期を取るにはデータのバージョニングまで行わないとでできないだろうが)

しかし、たとえ仕組みが出来たとしても、フロントエンドのオブジェクトが勝手に永続化されバックエンドとリアルタイムに同期していくというのは、シンプルであるがゆえに密結合へと誘導してしまう危険性もある。

Isomorphicな環境がフロントエンド・バックエンドの密結合をもたらしてしまった結果、パフォーマンスや拡張性などの様々な問題を生み出してしまうことになる。Meteorももれなく、そういった問題にぶつかったのではないかと想像する。

3.モノリシック(重厚長大)になりがちなこと

また、Isomorphicなアプリでは、モデルの操作含めフロントエンドにコードが集中していきバックエンドではほとんど何もしないということが多い。それはそれで一見良いようで、サーバで実行する方が効率的なものまで無理にクライアント側でやってしまうということが起りがちである。

実際にMeteorでは相当なモジュールをフロントエンドに作りこんでいるが、まともにプロダクションで利用できるレベルを維持していくのは大変だろうと推察する。

4.サーバサイドはどのみち意識して開発せざるを得ないこと

よくよく考えてみるとバックエンドを無視して作ることなんて実は不可能だということに気づく。

フロントエンド・バックエンドで同期するデータソースを実現し、また、バリデーションの定義を共通化したにしても、バックエンドでだけ実行すべき部分というのは必ず残るのだ。

例えば、IDなどのシーケンス番号の採番や、商品金額のマスターチェックなどは、整合性や不正が発生する可能性があるためフロントエンドだけでは実行できない。

サーバを意識しないのが無理なら、フロントエンド・バックエンドを区別無く隠蔽するのはむしろ開発を困難にしてしまうことにならないか。一見シンプルで作りやすそうで、エラー処理などによって複雑になってしまうのでは本末転倒である。

これについては、「node.jsで Isomorphic フレームワーク作ってみようとしたら超辛かった」を読むと困難さがよくわかる。

このように、Isomorphic化は苦労するわりには益が少ない。
開発生産性が劇的に向上するわけではないし、コードが奇麗にかけるとか自己満足の域を出ていないように思える。

Web系の人と組み込み系の人で意見がわかれるところかもしれないが、私はフロントエンド・バックエンドは明確にわけて記述する方がわかりやすいと思う。また、そもそも一人で開発すべきではないとも思っている。一人で作ろうとするから区別したくないと思うわけで、複数人であればそういう要求も出てこないのではなかろうか。

APIファーストとSPAはサーバサイドをスリム化させる


フロントエンド・バックエンドを分離してAPI主体で開発していく「APIファースト」と呼ばれる開発手法がある。

アプリ開発者はAPIのレスポンスだけを知っていればいいため、それぞれが独立して開発に集中でき、かつそれぞれをスケールさせることが容易になる。

最近、Micoservicesが注目されているが、API化はモノリシックなサーバサイドを分割してスリム化する効果が大きいことがわかっている。

弊社が請け負った実際のSPAアプリでの割合はクライアント6に対しサーバ1であった。
サーバサイドのスリム化を図りたい方はまずAPI化してSPA化するのがよいと思う。

API設計でCoC(設定より規約)が受け入れられるか


APIファーストのWebフレームワークでSynthというのがある。

これはフォルダをつくってや特定の方法に則った関数名をつけるだけで簡単にAPIが作れるもので、弊社のReflexWorksの考え方によく似ているのだが、「機能に影響するような新しい命名規則を覚えること」への批判もあって、今後どういう展開になっていくか興味をもって見ているところである。

実は、先日書いた記事でも似たようなことが指摘された。
サービスをそのままURLマッピングするのは筋がよくないという意見である。
URLにはそれが何をするものなのかや振る舞いが予想できるといった点が重要と考えています。
d/{リソースID1}/・・/{リソースIDn}
的な仕様の場合、どのように振る舞うのか想像できません。
静的コンテンツの場合はディレクトリにファイルを配置するのは自然なイメージだと思うが、サービスが絡んでくるとどのように振るまうかがわからないのでイメージするのが辛いということだった。これは一理あると思う。

ReflexWorksではサーバサイドでJavaScriptを実行できる。そして、ディレクトリの構造をそのままURLにマッピングできる。例えば、POST /s/booking をサーバで受けると、/booking.jsを読んでdoPost()関数を実行する。

これは、Restfulな新3層アーキテクチャで示すようなAPI表現 entity = blogic(entity) の考え方に基づいている。やろうと思えばIsomorphicにできると思うが敢えてやらない。

機能に影響するような新しい命名規則を覚えることの批判に対して、直感的な命名規則CoC(設定より規約)というスタンスが果たして受け入れられるのか、また、振る舞いをイメージしずらいという意見にも十分に応えられるのかが課題だと思っている。

負の遺産化を避ける


とはいえ、振る舞いをイメージしやすくするために適当で冗長なURIをつけるのもどうかと思う。

規約のないサービスAPIがいかに酷いものであるかは言わずもがなだ。

昔のSOAP時代によく見られた「お勝手API」はREST全盛の時代の今でも散見される。
REST制約が効いているとはいえ残念なURIになっているサービスはまだまだ多い。
そういうAPIにはお近づきにはなりたくないと思うのは私だけではないだろう。

BaaSがイマイチ流行らないのは残念なURIに付き合うコード書きたくないからだと思う。
技術的負債になるとわかっているから皆近づかないのではないか。
ロックイン、膨らむ学習コスト、不具合があったときのリスク、拡張性の欠如等々の技術的負債を考えてしまうのだ。

とにかくAPIを汎用的にすることで将来の開発に負の資産を残さないことが重要である。
ReflexWorksやSynthが主張するCoCで命名規則という発想は負の遺産化を避けることになるかもしれないと思うのだがどうだろう?

2つの現実解


ここまで、Isomorphicについては密結合・モノリシック肥大化の懸念がある一方で、APIファーストについては命名規則の批判や振る舞い表現に課題があることを示してきたが、結局どのように設計していけばいいのかについて最後に述べてみようと思う。

つまるところ、フロントエンド・バックエンドを同じコードで実行できなくても同様の効果をもたらすようなものを考えていけばよく、とりわけバリデーションの共通化とキャッシュ利用の2点は効果的だと考えている。

まず、バリデーションの一元化についてスキーマによる自動生成の仕組みを紹介したい。

これはReflexWorksでも採用している方法だが、共通のスキーマテンプレートからフロントエンド・バックエンドのバリデーションロジックを自動生成するというやり方である。

スキーマテンプレートについては、詳しくはこの記事を参照してもらいたいが、簡単にいうと項目名や型、正規表現のバリデーションルールなどを1項目につき1行で記述したものだ。

全く同じコードが生成されるわけではないが、正規表現などを使ったバリデーションはクライアントサイドとサーバで同じように動作させることができる。



詳しくは、12/10 のセミナーで説明するので、興味ある方はぜひご参加下さい。

「AngularJSとReflexWorksで始めるSPA開発 ~もうサーバー側で悩まなくて大丈夫~」

参加申し込み => compass

次に、キャッシュを利用した例であるが、firebaseでは以下のような機能がある。
publickeyより

 Firebaseを利用することで、すべてのクライアントとバックエンドのデータがリアルタイムに同期する、いわゆるリアクティブプログラミングが実現するわけです。
しかもあるデバイスがオフライン状態になっても、そのデバイスでのデータ同期が止まるだけでアプリケーションの動作に影響はありません。オンライン状態になった時点でバックエンドとのデータ同期が自動的に行われます。
このようにFirebaseでは、アプリケーション開発者がデータ同期の方法やネットワークのオンライン、オフラインの状態を気にすることなく、Webアプリケーションやモバイルアプリケーションの開発を可能にします。

実はReflexWorksのAndroidAPIも同様の機能を実装している。オフラインであってもリクエストを受け付けるし、裏で勝ってにバックエンドと同期を取ってくれる。

ただし、フロントエンドにあるデータはあくまでキャッシュであり、バックエンドにあるものとは時間差があることには注意を払う必要がある。また、バックエンドと同じ量のデータをフロントエンドに持つことも現実的ではないため基本的にサブセットになる。

要はこれで十分ではないかということがいいたい。

Isomorphicであることは、フロントエンド・バックエンドを意識しないコーディングができるというものであるが、それよりもオフラインでも使えるといったユーザエクスペリエンスの方が重要だと思う。

Isomorphicはほどほどに、ユーザエクスペリエンスを高めつつ、密結合にならないような現実解を見つけていければと思う。

水曜日, 11月 19, 2014

SPAとサーバサイドについて このエントリーを含むはてなブックマーク


SPA(Single Page Application)でサーバサイドをどうすべきかについて述べたいと思う。

MEANじゃダメな理由


今、MEANが注目されている。

MEAN(MongoDB, Express, AngularJS, Node.js)スタックが優れている理由 - Mozilla Open Web Day in Tokyoを終えて

MEAN(MongoDB, Express, AngularJS, Node.js)は、JavaScriptフルスタックのアーキテクチャで、データモデルとしてクライアントからDBまで一貫してJSONを使うという点でシンプルな設計が可能となっている。

業務ロジックにおけるO/Rマッパの割合は40%といわれるが、それが不要となって単純なJSONオブジェクトのやり取りだけで済んでしまうのは大変画期的だといえる。

しかし、MongoDBには、RDBMSでは一般的なトランザクション機能がサポートされておらず、スキーマレスという性質が品質面で悪い影響を与えている。Node.jsには、いわゆるコールバック地獄に陥りがちという問題がある。これらは業務アプリを開発するうえでは致命的な欠点にならなくもない。

ここにMongoDBのアプリ開発についてポイント突いた記事がある。

MongoDBでECサイトを実運用する3つのテクニック
1. トランザクション問題を回避する
2. スキーマをしっかりと設計する

これはMongoDBでもうまく設計すればECだって作れるよという内容のものだが、裏を返せばそれだけ大変な目に合いますよということだ。

ECサイトを構築運用してきた私の経験からいえば、これは、(乂´д`) No way! である。

ReflexWorksによる解決案


MEANは、要は、AngularJSによるSPA(Single Page Application)を中心にしたものであって、サーバサイドについてはJSONを返すREST APIが実装できれば何でもいいはずだ。

クライアント・サーバーの両方で実行できるisomorphic とまではいかないにしても、一貫してJSONやJavaScriptを扱え、かつO/Rマッパが不要であればそれで十分である。

ReflexWorksはMongoDBと同様にドキュメント指向であり、以下のような特長をもつ。
  • XMLやJSONなどの構造化データの読み書きができる。
  • ACIDトランザクションをサポートしており、シンプルなTransaction ScriptパターンでAPIが実行される。
  • AngularJSのMV*アーキテクチャーと相性がよく、ModelとViewに集中して開発ができる。
  • アプリケーションで定義するソフトスキーマを採用、項目の追加が容易である。
詳しくは、12/10 のセミナーで説明するので、興味ある方はぜひご参加下さい。

「AngularJSとReflexWorksで始めるSPA開発 ~もうサーバー側で悩まなくて大丈夫~」

参加申し込み => compass

SPAの優れた開発生産性


SPAでは、JavaScriptでJSONデータを処理し、ページを再読み込みせずに動的にページを更新する。

ユーザーが操作するたびにサーバーにリクエストを投げてHTMLを生成するよりも、クライアントだけで動的に画面を更新するほうが早くて効率的である。また、ユーザーの操作に応じてインタラクティブに動くリッチクライアントを実現できる。

パフォーマンスやSEO対策で問題があるといわれるが、業務アプリにおいては、ビューのパフォーマンスが問題になることは稀であり、SEO対策についても今年の年末にSPAのURLをGoogleがクロールするようになるという。(生まれ変わるAngularJSより)

むしろサーバサイドでのHTMLの事前レンダリングから解放されるメリットの方が大きく、特に開発生産性については相当なアドバンテージがあると考えられる。

Yeoman、Bower、Gruntなどのビルドツールやapi-mockなどを利用すればサーバサイドのことは意識しなくてよい。

毎回サーバにデプロイする必要がなく、ファイルの変更を監視してブラウザのリロードをかけたりできるので、開発者はストレスなく開発できる。

さらに、CircleCIやTravisCIによる自動テスト&デプロイも利用可能である。

サーバサイドでレンダリングはオワコン


たまにAngularJSとサーバサイドの両方にビューの機能を持たせる話を耳にするが、早く諦めてサーバサイドはAPIだけにすることをお勧めする。さらにスマホ向けAPIも共通にできればアーキテクチャーとしては大変スッキリする。



でもこれはLAMPを得意とする開発者にとっては苦痛を伴う環境の変化になるだろう。

ふとデジャブ感におそわれたので調べてみたら、8年前の2006年11月に書いた記事が見つかった。

楽をするために苦労を厭わない人たち - Seasarカンファレンス(秋) -

「しかしWeb2.0の主役になれなかったからといって、今更、LAMPの生産性に追いつこうと思っても、とうてい無理に思える。
(中略)Webアプリケーションをサクサク作るっていわれても、本当にそのようにできるのか。私はJavaで作るWebアプリという発想そのものが疑問符「?」である。LAMPの台頭で、それがもう否めない時期にきているのではないか。」

このときJava全盛でEoDとかいってたのを覚えているが、結局はRoRをはじめとするLAMPの生産性にはかなわなかった。しかし、8年経った今はこう言い換えることができる。

「LAMPがSPAの生産性に追いつこうと思っても到底無理に思える。私はLAMPで作るWebアプリという発想そのものが疑問符「?」である。SPAの台頭で、それがもう否めない時期にきているのではないか。」

8年前のLAMPが今のSPAといえなくもない。

最後に


ここまで一気に書いてから rebuild67a を聞いた。
どうやら、AngularJSが嫌いという人が増えているとのこと。
困ったな。


水曜日, 8月 27, 2014

RESTとJSON、スキーマ定義について思うところ このエントリーを含むはてなブックマーク


 mozaic.fm #7 REST#mozaicfm REST を聴いての感想、それから「Web+DB vol82のWebAPIデザインの鉄則」に触発されたので書こうと思う。

REST設計について


WebAPIを設計するうえでRESTが重要であることは周知のとおりである。

“Constraints are liberating”「制約は自由をもたらす」

@t_wadaさんがおっしゃっているように、RESTを前提にすれば、「アーキテクチャとしてもそうだし、アプリケーションフレームワークも「適切な制約」を設けることで設計のコストが下がる」という大きなメリットが生まれる。

しかし、相変わらずリソース設計やらインターフェース設計やらで悩んでおられる方も多いと聞く。 その一方で個人的には適切なフレームワークを使えばREST設計で悩まなくてもよいはず(※3)という思いもある。

インターフェース設計などの基本設計をフレームワーク側で用意してあげれば、普通に設計していくだけで自然とRESTfulになっていくような開発が可能になる。これはReflexWorksが目指しているところでもある。

そういう意味で、ちゃんとしたフレームワークさえあれば、今は開発者がRESTの基本をしっかり学んですべて設計する時代じゃないかもしれないと個人的には思っている。

URIの制約をどうするか


ではどのような制約を設ければ自然とRESTfulに設計できるのか。
まず私が一番大事だと思っているのはURIの制約である。

URIはリソースを指し示す重要な表現であるが、これは単にリソースの場所を示すものであって、表現形式や検索条件など他の情報を含めるべきではないと考えている。(ただし、クエリパラメータは別)

例えば、以下のURIはよくあるものだが、このなかにはバージョン番号や「API」で「datastore」であるということ、また階層によってコレクション、またはエントリであることを説明する情報がURIに含まれてしまっている。

/v1/api/datastore/{コレクションID}/{オブジェクトID}

私はこれをURIの「お仕着せ」と呼んでいる。

一方、単にリソースの場所を示すURIは以下のようになる。

/d/{リソースID1}/・・/{リソースIDn}

これにはバージョンやAPIなどを示す情報はなくコレクションやエントリの区別もない。
階層が/によって多段に表現されているが単にリソースがあることを示すだけである。
例えば、{リソースID1}/{リソースID2} は、リソースID1の配下にID2があることを示しているが、ちょうどファイルシステムのフォルダをイメージするとわかりやすいかもしれない。

/d/app/index.html とすれば、/appフォルダの下にindex.htmlが入っているというように直感的にわかる。これを、リソースにおいても/d/invoice/master/・・ などと階層で表現したいと考えている。

ちなみに、/v1/api/datastoreも階層表現されているように見えるがこの階層には意味はない。その証拠に、/v1_api_datastoreとしても同じことである。またURIの設計上だけで実際にエントリがあるわけでもない。
これはURLが単に固定されているだけの話であって制約というより不自由なだけである。

繰り返しになるが、URIにリソースのアドレス以外の特別の意味を持たせてはいけない。
そういう固定的でお仕着せ的な設計がAPIの齟齬を生む。そして最終的には管理できなくなりバージョニングが必要という話になってしまう。

URIが単にリソースを示すだけであれば、APIの仕様を見ながらコーディングする必要もなく、バージョニングも必要ない。(どうしても必要なら?v1とかクエリーパラメータで区別すべきというのが私の意見。<関連> You Need Not API verson2

ただ、先日ある開発者に、ReflexWorksにはGoogle Drive APIのような一覧ってないのですか?って聞かれたときには、世の中はそういうものなのかなあとは思った。(その開発者にとってみれば制約よりも仕様の方がわかりやすいということだった。(※1)

オブジェクトの中の項目をURIに含めるべきか


JSONオブジェクトの構造中の特定の位置を指し示すJSON Pointerというのがある。
例えば、以下のオブジェクトが/d/masterに入っていたとする。


{
  "customer" :[
    { "name":"foo" ,"id": "1"},
    { "name":"bar" ,"id": "2"},
    ・・・
  ],・・・
}

JSONPointerでアドレスを/customer/0/idとすれば、customer配列の0番目のidという意味になる。これを、URI(/d/master)にアドレスを直結して、/d/master/customer/0/id と表現するのはあまりうまくない。

なぜなら、先ほどのリソースの定義では、customerの下に0というリソースがあり、その下にidというリソースがあることになってしまうからだ。

HTMLの場合は同一文書内のフラグメントには#を使うが、リソースも同様であり、あくまでmasterの中なので、私なら/d/master#customer[0].id と表現する。この方がJSONっぽくて自然だと思う。

クエリパラメータによる表現の区別


では、リソースの階層表現においてコレクションやエントリをどのように区別すればいいだろうか。

私はクエリパラメータを使って区別すればいいと考えている。
実際にReflexWorksでは、?f(feed)でコレクション、?e(entry)でエントリを意味する。

また、リソースというのはデータの集合であり、その表現はJSONやXMLやその他どの様なフォーマットであっても同様に扱えるのが理想である。ReflexWorksでは、デフォルトはJSONだが、?xでxml、?mでMessagePackのフォーマットで表現できる。

もっといえば、HTMLコンテンツであっても画像であっても同じリソースと考えることができるわけで、GET /d/app/index.html でも、 GET /d/masterでも区別する必要はない。
フレームワーク実装の難易度は上がるかもしれないが、こうすることで全てのリソースに対してRESTで統一できる。


クエリパラメータを使っての条件絞り込みは直感的でよい実装だと思う。ReflexWorksでも、?の後ろに絞り込み条件を複数指定することができる。そのときのパラメータは以下のようにエントリの中の項目(subInfo.favorite.foodやpriceなど)となる。-eq-(=)や-lt-(<)は条件式である。
例) 
http://xxx.xxx/{URI}?f&subInfo.favorite.food-eq-egg&price-lt-5000&l=20
リソースを直列分割して扱う「?fields」というパラメータをよく見かけるが、個人的には好きにはなれない。これは、指定されたフィールドのみを対象としたリソースを得るために使われるものだが、リソースの最小単位=エントリの括りがなくなってしまい曖昧になってしまう。

上記の絞り込みの条件で得る結果はフィード(コレクション)もしくは、エントリであるべきだ。

リンクがない問題と語彙の導入


XML全盛の頃はATOM FeedのセマンティクスとRESTによってルールはうまく整理されていたように思う。(結局流行らなかったが)


ATOMの語彙は以下のようなシンプルなものであった。

title
id
link
 title
 rel
 href
content
・・

しかし、JSONによってXMLが駆逐されてしまった。それに伴い、SOAPもWSDLもATOMも無くなった。JSONがもたらした不毛の荒野にはXMLやATOMが培ってきた財産(スキーマやリンク)は跡形無く見当たらない。

リンクのないデータは扱いにくい。PrimaryKeyのないデータと同じようなものだ。
JSONにはメディアタイプとLINKヘッダという2つの解決策があるらしいが、エントリに入らないと扱いにくいし、特にコレクションをどう表現するかが課題だと思う。


JSON Schemaでは以下のようにidやlinksという項目が導入されている。

title
id
type
links
 title
 rel
 href
 mehod
 mediaType
 targetSchema

リンクについていえば、AtomよりJSON Schemaの方がむしろ語彙数が多くなっているのは皮肉である。Atomに比べてJSON Schemaはむしろ冗長になっている。
リソースは自由に変換されるべきということは述べたが、私はリソース定義にはmethodやmediaType、targetSchemaという項目は必要ないと思う。

結局、ATOMの語彙でよかったのではないか。ATOMのJSON表現で。

JSONでスキーマを書くべきか


JSONもちゃんと設計したいということで構造を含むスキーマ設計がやりたくなるのはわかる。それに、スキーマレスだから
設計しないでもOK(ヒャッハー)というわけにはいかない。特に業務システムであれば。

きちんと設計したうえで必要なときにいつでもスキーマを変更できるような柔軟なソフトスキーマが望まれていると思う。

ただAtomやXMLを削ってJSONを拡充するという発想は何か間違っている気がする。


今はJSON全盛とはいえ、JSON SchemaはWSDLと同じ匂いがする。
ATOMに比べても語彙数が増えている。JSONに語彙を追加するのはHTML5で語彙を追加するのとはわけが違うと思うのだが、それでもなお、似たようなものを導入するつもりなのだろうか。

たしかに、JSONはXMLよりシンプルで、基本的な値などをやりとりするだけであれば、XMLよりもJSONの方がずっと簡単だし、JavaScriptを使っているのであればJSON形式は自然な形式である。

しかしスキーマは別の話だ。何でスキーマをJSONで定義するの?という素朴な疑問は湧いてくる。


構造を定義するのにお世辞にも読みやすいとはいえないJSONを使う意味は本当にあるのだろうか。

XMLはHTMLに似て宣言的なのだしマークアップに適している。スキーマ定義も宣言的なのだからXML使う方がまだましではないか。

リソース定義のためのシンプルなスキーマ言語の必要性


そして、そもそも考える順序が逆ではないかということに気づく。

先に述べたように、JSONはリソースの一つの表現にすぎないわけだから、リソースはJSON前提で考える必要はなく、逆にJSONに縛られて考えてもいけない。

JSONを拡充する方向で上がっていってもだめで、つまりは、「JSONやXMLなどの表現にとらわれない汎用的な」リソース構造を定義するスキーマ言語があればよいという考えに至る。(※2)

例えば、以下のように、項目名や型、バリデーションルールなどを1項目につき1行で記述できれば便利である。ルールは、()に型、省略でString、{}に値の範囲、=の右辺に正規表現、!で必須項目などとする。



master
 customer
  name=^.{0,50}$
  id(int){0~999999}!
  ・・・


きっとEXCELの定義書でも1行1項目で管理しているだろうから、そこから書き起こしたテンプレートに簡単にルールを記述していける。

JSONは可読性においてXMLに及ばず、利便性においてEXCELに及ばない。
JSON Schemaで管理するのはいいが結局はEXCELを捨てきれないだろう。
プロジェクトの共有文書が相変わらずEXCELのままであれば、JSONSchemaで管理すると言った人が2重メンテするハメになるのは目に見えている。

ReflexWorksでも上記のようなシンプルなスキーマ定義を採用している。そして、スキーマからエンティティオブジェクトを自動生成している。


テンプレートに項目名を記述することで自由にスキーマを定義でき、運用中に項目の追加変更が可能である。ソフトスキーマであり更新すると直ぐにシステムに反映する。

また、型の指定、親子関係や繰り返しといった構造の定義、必須チェックや最大/最小チェック、バリデーション、Index、暗号化などを指定できる。

(詳しくは、仕様の「テンプレートによるスキーマ定義」を参照してください)


(追記)

(※1) このように感じる方は意外と周りに多い。特にサービス(ビジネスロジック)を含めて考えると可読性や振る舞いの予想という意味でURI固定の方がわかりやすいという。ここではあくまでサービスを含めない静的なリソースのことを主に想定している。(ReflexWorksの/dは静的なdataの意味で、サービスは/pのプロバイダというように使い分けている。)

 (※2) JSONとXMLは等価ではないし互いに可換でもないので、1つのスキーマで両対応は無理。サブセットしかサポートできないというのはおっしゃるとおり。https://twitter.com/makotokuwata/status/504954681085263872 ただ、構造や型、バリデーションなどを汎用的に定義することは可能だし、ReflexWorksで実際にやっていることでもある。


(※3) 悩まなくなったらものづくりの仕事つまんなくない?との意見をいただいた。

日曜日, 3月 16, 2014

You need NOT API version 2 このエントリーを含むはてなブックマーク


バージョン番号をURLに含めるべき「でない」理由について、 自分はどのように考えているか具体的に述べたいと思う。

見過ごせない根本的な問題



基本的には、 http://rebuild.fm/35/ で述べられていること、それから、

http://blog.kazuhooku.com/2014/03/web-api.html http://yohei.hatenablog.com/entry/2014/03/12/001707 

 に同意である。  そこで述べられているように、

 ・拡張が容易である
 ・拡張時に後方互換性を破壊しない 

をしっかり守り、互換性を壊すことがないようにできるだけ変更を少なくしたうえで、 互換性を壊す必要がある場合は関数名を変更する、といったような感じで概ね解決できると思う。 

ただ、何というか、根本的なところで琴線に触れるものがあって、見過ごせないなあと感じている。 率直にいうと、そもそもAPIのv2を作りたくなるような状況に陥ることこそが問題ではないかと思っている。 

JSONのような拡張可能なシリアライズフォーマットを用い、拡張可能な形で入出力パラメータを設計する。これは、いわゆるスキーマレスとかソフトスキーマといわれるもので、項目が変わったとしてもシステムに影響ないような仕組みである。つまり、項目が変更されても影響なければバージョン管理はそもそも必要ないというわけ。

これはJSONだけでなくXMLやMessagepackでも同じことがいえる。 

メディアタイプでデータ表現を変えるという発想はReflexWorksに近いというか、そのものなんだけれども、RESTのリソースの概念では表現を変えてもデータは同じものであって、v2なんていう発想はない。 (もちろん、データが更新されたらリビジョンは上がるがAPIのバージョンに相当するものはない)

それから、私はJSONの文化大革命って呼んでいるのだが、過去にHTMLやXMLで積み上げたセマンティクスの議論が台無しになっている雰囲気があって、今回のようなAPIでバージョニングすべきかが問題になるのは当然の帰結のような気がしてしかたがない。 

今さら、ATOM PUBとかいうつもりはないけれど、それにしても、勝手気ままにAPI作りがちではないか? 

最悪なのは、getXXX,updateXXX というように単にエンティティに操作をつけただけの関数をモッサリ作ってる。 だから、/v2/getXXX なんて欲しくなるわけ。データこそJSONかもしれないが、これこそSOAP時代の発想じゃないか。 

HTTPのGETやPOSTほど変更に強いAPIはないわけだし、RESTで単純にHTTPメソッドへマッピングして、GET|POST /XXX でいいじゃん、と思う次第。

CRUDに当てはまらないAPI


一方で、単純にCRUDに当てはまらないものはどうするんだ?RESTでCRUD4つに集約できるなんて幻想だ!なんていうのもわかる。 

基本的にリソースは静的なデータであって、GET /XXXとすると、/XXXのデータがそのまま返る。 しかし、/XXXの結果を加工したくて、サービスとしてサーバサイドで動的にビジネスロジックを動作させたいこともある。 

例えば、消費税計算関数calcがあり、デフォルトは5%であるとしよう。以下のように書くことで、calc(/XXX)を実行する。つまり、/XXXで取得した原価のフィードを関数calc()で消費税5%で計算して返す。 

 GET /XXX?f=calc   

さらに、8%で計算するv2も定義してURLパラメータで表現する。4月以降はv2をつけて実行すればよい。 

 GET /XXX?f=calc+v2 あるいは、GET /XXX?f=calc&v=2 など。 

つまり、URLパラメータでバージョニングすればよいという結論である。

結論からいえること


いつのまにかURLにバージョン番号をつけるべきという結論になってしまっているが、言いたかったのはリソースはあくまで静的なものであって、リソースをCRUDする限りにおいてバージョニングは必要ないということ。

また、リソースの項目の追加変更においても必要ない。唯一、必要になるのはCRUDに当てはまらない一部の動的なサービスAPIであって、それでもURLパラメータで管理すればよく、API全体をバージョニングする必要はないということ。

だから、/v2/~というように、URLにバージョン番号をつけるべきではないと思う。

日曜日, 2月 02, 2014

PC遠隔操作事件、状況証拠だけで有罪にすべきでない理由 このエントリーを含むはてなブックマーク


PC遠隔操作事件で初公判を2月に控えた今、ネットでは色々酷いと叩かれている。

逮捕当時は誤報だらけで最近沈黙しているマスコミ、4人の誤認逮捕をしでかした警察、予断と偏見を持たずに証拠を見れば有罪と言っている検察、そして、勾留して一年が経とうとしているのに未だに保釈どころか家族の接見すら許可しない裁判所。

この事件はK氏が真犯人であることを示す決定的な証拠が一つでもあれば有罪になるのだろう。しかし、決定的証拠はないといわれ、状況証拠のみで争われるという。そうなると冤罪が怖い。

状況証拠だけでは冤罪が生まれる


ITを悪用した遠隔操作事件では誰が“犯人”に仕立て上げられたとしても不思議はない。
肝心の「パソコン遠隔操作事件」の検証がどうなっているのかが、僕らの一番の関心事です。しかも警察や検察は、これまでに何件もの誤認逮捕事件を引き起こしているわけです。言い換えれば、誰が“犯人”に仕立て上げられたとしても不思議はなかった事件だと言うこともできるでしょう。検察は事件の真相を解明し、同様の事件の再発を防ぐことができるんでしょうか?  yahoo newsより
これがもし冤罪だったら恐ろしいことだ。
とにかく状況証拠だけでは有罪にならないようにしてもらいたい。


犯人を示す2つの証拠


しかし、そういう私はK氏が真犯人だと確信している。

それは、江ノ島のグレーという猫に触れたこと、またスマホ携帯の検索履歴という状況証拠があるから。PCの検索履歴、雲取山に行ったこと、悪の教典の本、まどか人形などの639点の状況証拠は弁護人のいうように証拠ゼロに等しいが、この2点、とりわけスマホ検索履歴は犯人性の推認力が高い有力な状況証拠ではないだろうか。

猫にK氏が触れた時間を含むわずか20数分の間に首輪がつけられていた。間違いなくこの時間に真犯人はいたわけで、K氏が犯人ではないとすると他の誰かということになる。しかし、この短時間に触れた人が大勢いたとは考えにくい。

また、K氏はスマホで「猫 首輪」など事件に関係するキーワードを1月2日に検索している。1月4日以降であればニュースを見たからと言い訳ができるがその日は犯人以外は誰も知らないはずである。またK氏は、神保 哲生でも検索している。K氏はそのような人物は知らないと供述していた。

PCと違いスマホでは検索履歴を残すような遠隔操作はできない。お正月ということもあり会社の同僚がK氏の目を盗んでやったということも考えにくい。

それでも無罪とすべき理由


このように、K氏は非常に怪しいのである。が、どんなに怪しくても私は真実より事実、つまり決定的な証拠がないということを重く見るべきだと思う。

もし状況証拠だけで有罪になるという判例を作ってしまったらネットは冤罪だらけになってしまうだろう。(ネットというものは本来そういうものである。)

ではK氏が本当に犯人だったらどうするのか。私はそれでも無罪とすべきと思う。求刑10年はいくらなんでも重すぎるし、もう彼は自分が犯した罪以上の罰を受けているではないか。

それより、実際に普通の市民が犯罪者と疑われ4人が誤認逮捕させられたことを重く見る必要があるだろう。

冤罪は人ごとではない。明日は我が身と考えるべきだ。

日曜日, 1月 19, 2014

次世代Webアーキテクチャーの話 (CROSS2014を聞いて) このエントリーを含むはてなブックマーク


 CROSS2014の次世代Webセッションを見て来た。
セッションの前半で議論されていたプロトコル編はしっかりとした方向性が示されていたが、後半のアーキテクチャー編は現状の混沌とした話が多くて、方向性というか新しいビジョンを示すまではいけなかった印象だった。

それは、サーバのアーキテクチャーが成熟していることも理由の一つなのかもしれない。
しかし、アーキテクチャーこそ方向性を示すのが重要だろうと思うので、個人的に考えていることをまとめることにした。 

Webスケールを実現する技術とリアルタイムWebの方向性


  リアルタイムWebというわけではないが、密結合なプロトコルはことごとくインターネットで玉砕されてきた歴史がある。 

古くはCORBA IIOP、DCOMの失敗。それからSOAPの失敗。 
(ちなみに、IIOPのIはInternetで、当初は大規模なインターネットスケールで分散させようとしたことがうかがえる) 

これらは何でうまくいかなかったのか。 

それは、簡単に言ってしまえば、単一のマシンから複数のマシン、そして、インターネットへと拡張していく際に失うもの、つまり整合性や信頼性の問題を考慮してなかったからだと思う。(詳しくは補足を参照)

 IIOPにしてもSOAPにしてもライブラリのAPIのような形で提供して、それをインターネットを跨いで同期的に実行することを想定していた。
 
これらは整合性や信頼性の問題を解決することなしに、単一マシンのアーキテクチャーをそのままインターネットの信頼できないマルチ・マシン上に拡張してしまったのだ。 
(かつてIBMでCORBAのプロジェクトに多少からんでいた私は、インターネット上では通用しないことを嫌というほど味わった。早く気づくことができていたら(特にIBMは)莫大な投資をせずに済んだのに。) 

ところで、リアルタイムWebの方向性はどうなんだろうか。

将来においても、既存のWebアーキテクチャの延長線上にあるものなのか。それとも、別の違う方向を進むのか。 また、何かリアルタイムWeb技術が急速に進んで、既存のWebアーキテクチャーがそれに置き換わるというような話になるのか。 

クライアントとサーバが密に結合した形になってしまうと前述したようなトラウマが蘇ってくる。 

プロトコルの高速化によりある程度の適用範囲は広がるとは思うが、それでも局所的にしか解決できないだろう。 逆の言い方をするとリアルタイムWebを追求していくとNWやプロトコルの高速化でしか対応する術はないように思える。 

一方、既存のWebアーキテクチャというのは、http(s)とかプロトコルの話だけでなく、RESTなどを含むアーキテクチャスタイルの話が中心になる。

これは、permalink(URI)で示すリソースを操作する概念であり、基本的にステートレスである。 あらためて説明するまでもないが、RESTの「リソース」概念は次のようなものである。

・インターネットのすべてのものがリソースである。
・すべてのリソースは、URIによってアドレス可能である。
・リソースに対する操作は、HTTPの基本的なオペレーション、Get, Put, Post, Delete によって定義される

普通のWebサーバは基本的にはスタティックなホームページの配信を行い、動的なWebにしてもサーバで都度生成したものを返しているだけだ。

Webスケールといった言葉があるように、これはスケーラビリティに優れており、また、ネットワーク耐性にも優れている。

RESTは要はそのWebのアーキテクチャースタイルを壊さないようにWebサービスに応用したものである。 

また、キャッシュも重要な技術要素の一つである。これは、「誰か他の人のリソースを利用し、あるいは、他の誰かが、我々のリソースを利用することを認める」ような世界である。

Webスケールを実現できているのは、ブラウザキャッシュ、PROXY、あるいはCDNといったキャッシュ技術があるからといっても過言ではない。

他方で、ワンセグTVがフルセグTVより数秒のディレイがあっても気にする人はいないように、そもそも本当にリアルタイムである必要があるかという話がある。 

別のいい方をすれば、非同期的な更新で、eventuallyであっても十分ではないかということ。 例えば、一般的なWebアプリのユースケースを考えてみると、そこまでオリジナルにこだわる必要はなく、コピーの若干古い情報だとしても実用上問題はないケースも多い。むしろ、完璧に同じタイミングじゃないとまずいケースの方が少ないのではないか? 

これは妥協しろと言っているのに等しく、低レイテンシーを求めるリアルタイムWebとは相容れない考え方かもしれない。 

なので、個人的には、既存WebとリアルタイムWebは別の方向性をとり、リアルタイムWebが既存Webに置き換えられていくこともないだろうと考えている。

全体的な方向性としては、リアルタイムWebはそれはそれで盛り上がりつつも、既存Webのアーキテクチャー上をベースとして今後も進んでいくのではないだろうか。 

データのキャッシュ化を実現するためには


 一つの解として、HTMLなどがWebサーバやブラウザでキャッシュされるのと同様に、データにおいてもキャッシュさせるという考え方がある。 

そもそもデータは動的であり揮発性の高いものであるが、生成する際にバージョニングして静的に扱えるようにすることで、HTMLなどの静的コンテンツと同様、キャッシュを効かせることができればスケーラビリティは高くなる。 

バージョニングとは、例えば、データはレコード単位にURI+リビジョン番号を振ることでユニークであることを保証し、それが一致している限り同じデータとみなすような仕組みである。更新されるとURIは変化しないがリビジョン番号はカウントアップされる。 

正のデータはサーバ上にあって常に誰かによって更新されている。他のシステムはそのコピーを持ち、正のデータが更新されるとEventuallyに更新されることを前提に考える。ここでは遅延を許容することが肝要である。 

また、同じデータが複数のクライアントやサーバに散在している可能性も考慮し、更新する際は何回更新されても同じリビジョンであれば同じ結果になるようにする(べき等性を考慮する) 

ただし、データの整合性についてはシビアに考えなければいけない。これには、更新の際に元データのリビジョン番号が異なれば他者による更新があったということでエラーとするような楽観的排他の仕組み(Snapshot Isolation)で対応できる。

これにより、整合性の問題をWebのアーキテクチャースタイルを壊さないで解決できるようになる。 

去年だったか、permalinkの重要性は薄れて短縮リンクで十分という議論もあったが、整合性まで考えるとやはりpermalinkは重要であるように思う。短縮リンクはpermalinkのaliasという位置づけであろう。

サーバ負荷軽減のためには通信しないこと

 
 注意しなければならないのは、既に同じリビジョンのデータを持っているにもかかわらず、何度もサーバにリクエストを投げてしまうようなことである。これではキャッシュしている意味がない。 

CROSSセッションの議論でも、シングルページアプリケーションのデメリットとして、サーバへの通信回数が増えて負荷が大きくなるという指摘がなされていた。

「long pollなどNativeと同じで不必要な通信が多く発生するリスクがありしばしば作り方が問題になる。 初期化のタイミングがなくなる反面、サーバへの通信回数が増える。 最悪、ステータスコードを見ないでリトライするようなクライアントコードが書かれるリスクも考慮しないといけない」

シングルページだとパフォーマンス管理が難しくなったという意見であるが、そもそもサーバサイドのレンダリングを引きずられていると高速化は難しく、たとえデータバインディングに特化できたとしても、効率よくしないと今度はそれがパフォーマンスに悪影響を与えてしまう。

ステートレス性というか、キャッシュを利用してなるべく通信を発生させないようにする。また、発生しても必要最低限のデータに絞ることが大事である。

究極の形態がオフライン耐性をもったアプリであり、Offline Firstのような概念や方向性をもって作っていけば、おのずとNW効率のよいものが出来上がるのではないかと思う。 

クライアント・サーバ間のデータバインディング

 
 クライアント・サーバ間のデータバインディングは、具体的にはAngularJSに代表されるようなMVVMに加えてサーバとのデータバインド機能を追加するようなイメージとなる(図)


これまでの、クライアントからのリクエストによってサーバ上で動的にコンテンツが作られるスタイルから、基本的にクライアントとサーバが同じデータを参照しており、どちらかが更新したタイミングでお互いに通知されて同期されるような、クライアントとサーバとの双方向バインディングのスタイルに変化していくと思う。(リアクティブプログラミングという)

これからの時代、このようなクライアント・サーバ間のリアクティブプログラミングなどが求められると思うのだが、一歩間違えれば密結合の世界になって、スケールしなくなるのも懸念される。 

同期的な密結合ではスケールしないことは述べてきた通りであり、それは、XML/SOAPでもREST/JSONでも同じことだといえる。
 
実際、WebサービスのAPIは今ではREST/JSONが主流であるが密結合になってしまって残念なものも多く見られる。 

クライアント・サーバ間のデータバインディングでは、図にあるように、XHRでリクエストを受けつけるが、レスポンスについてはWebSocketで非同期更新する。

バージョニングされたデータが非同期更新されるのがポイントであり、WebSocketであっても同期的な密結合に陥ることはないのが特長である。 

あと、バリデーションの問題(サーバとクライアントでどうバリデーションするか)といった新たな課題もあるが、長くなるのでここでは触れない。

通信のデータ形式、それから、BaaS

 
 クライアント・サーバ間のデータバインディングでは、通信のデータ形式はXMLでもJSONでもMessagePackでも何でもいい。 むしろ同じように扱えるべきだと考える。 
データとは本質的には普遍的なものだから。

よく見かける、XML vs JSONとか、SOAP vs RESTなどの議論はたいへん不毛である。
データ表現を変えたところでアーキテクチャーが密結合であれば意味がない。

ちなみに、弊社のReflexWorksは様々なデータ表現に対応している。
 (先日、既存のReflexWorksシステムにおいて、XMLからMessagepack+Deflateに変更したところ処理速度が4倍(1034msから250ms)、データ量が2%(892,799bytesから21,255bytes)になった。なんと98%削減できたことになる。業務ロジックに変更はない。) 

現在、AngularJSのクライアントとReflexWorksサーバ間との非同期データバインディングが可能なBaaS、vte.cx engine(ぶいてっくす えんじん)を開発中である。

ソフトスキーマと項目単位に付与できるACLやグループ機能などのSNS機能もある。 

乞うご期待。 

(補足:http://stream.itmedia.co.jp/enterprise/pdf/pdf_16_1235461490.pdf より)
 

・整合性の問題
マルチ・プロセスからマルチ・マシンへ拡張するとき、我々は、状態を失う。「システム」のグローバルな状態というのは、虚構である。興味深い分散システムには、整合的な状態というものは存在しない。(Lamport:http://research. microsoft.com/users/lamport/pubs/pubs.html)分散OSのプロジェクトは、グローバルな状態を導入しようとしたが、大々的に失敗した。
しかし、障害の局所化(何かまずいことが起きても、システムの部分は生きのこる)を手に入れた。

・信頼性の問題
(インターネットの)信頼できないマルチ・マシンたちに拡張するとき、誰を信ずることが出来るか分からない難しい状況の中で、我々は信頼を失う。しかし、我々はスケール(webスケール、インターネットスケール)」を手に入れた。
 


火曜日, 1月 14, 2014

Web API認証について このエントリーを含むはてなブックマーク

 最近、Web APIの認証をどうすべきか考えている。

 例えば次のようなケースをどうするか。
 「既存のWebサイトがあり、既にユーザIDとパスワードによる認証によって、ブラウザでデータを提供している。 今回、この提供データをブラウザの画面ではなく、REST APIにて取得可能にしたい。 このデータはユーザ毎に取得可能な値が違うので、認証、または認可によって制限をかけたい。」

 ユーザーがブラウザからIDとパスワード(以下ID/PW)を使ってログインする方式を、そのままWeb APIにも適用しても安全なのだろうか。 Web APIの先にはスマホアプリやシェルスクリプトなどから直接ログインするものなどが考えられるが、安全かつシンプルに実装するにはどうしたらいいのだろうか。 

私はセキュリティの専門家ではないので間違った考え方をしている可能性もあるが、誰かの目に留まって助言いただけるかもしれないので、それを期待して今考えていることを率直に述べようと思う。

APIで安全な認証はそもそも作れるか

 
 そもそも認証については原則ブラウザを使わないとダメなんじゃないかと私は考えている。
 その理由は、ブラウザじゃないとサイトの安全性について確認することが困難だから。
ブラウザであれば、利用者はアドレスバーでドメイン名を確認できるし、https通信されていることやベリサインシールも確認できる。その逆にアドレスバーを隠してあるサイトは信用できないしパスワードを盗む偽サイトかもしれない。
 世の中を見渡すと、ほぼ全ての認証の基本になっているのがブラウザからのログインケースであるように思う。

 一方、API認証で、たとえID/PWで認証できるようなAPIを作ったとしても、ブラウザのようにサイトの信頼性を確認する手段がないため、利用者が安心して使えるものを作るのは難しいのではないかと思う。
 例えば、コマンドラインのプロンプトやスマホアプリの画面でID/PWを入力するアプリを作ったとすると、どこで認証するかをユーザは正しく認識する必要があるが、送信先URLに不正がないことなどを確認するのは難しいと思う。(※1)  
 ただ、利用者に毎回ID/PWを入れさせなくてもアクセス制御さえきちんとできれば十分というケースもあるだろう。このようなケースであれば後述するOAuth2.0や認証キーを使う方法などがある。

API認証のパターン


 認証(認可)の方法は、大きく分けて以下の3つがあると思う。 


1.ID/PWを送信するBasic認証やDigest認証(含むWSSE)  
2.(ブラウザの管理画面等で発行する)認証キーを使う方法  
3.OAuth2.0 アクセストークンを使う方法  

1.のBasic認証APIでID/PWをハードコードして使うケースはよくありがちで、このような安易なやり方はセキュリティ上まずいのはわかる。 APIはプログラムから利用されることを前提として作られているため,PWリスト攻撃/辞書攻撃を試みるクラッカーにとって攻撃に役立つ便利な機能になり格好なものになることは容易に想像できる。

 2.の認証キーを使う方法では、まず管理者がログインして画面を開き、そこに表示されている認証キーを使ってAPIからアクセスする。MBaaSでもよく使われており、ID/PWを入力しないため安全な方法といえる。

 3.のOAuth2.0では、アクセストークンをAPIの認証キーとして使う。アクセストークンを入手する際に最低一回は必ず、正規のWebのログイン画面で認証する必要がある。 

DropboxUploader(https://github.com/andreafabrizi/Dropbox-Uploader)はbashでOAuth2.0を使っている。(しかし実際に導入してみるとわかるのだが設定がちょっと面倒くさい) 

OAuth2.0は「認可」である。これを使うということは、つまり、認証はAPIでは行わないことと同意となる。認証キーを使う方法も、Authorizationヘッダに付与するOAuth2.0 Bearerも同様に「認可」の一部であると考えられる。認可はユーザによって正しく認証されていることが前提であり、この方式を使えばパスワードが罠サイトに送信されるような心配もない。 

2.と3.は認証と認可を別々に分けることでリスクを減らすという考え方で、これらを採用できるのであれば特に問題はないし、また冒頭に挙げたケースもこれで解決できるかもしれない。 OAuthのアクセストークンを使えばAPIで認証やる意味はないという意見もアリだと思う。ということで、OAuth2.0などの認可の話はここで終わり

 問題は、1.のような「認証」をAPIでやってよいかどうかということである。  

次にこの認証の論点に絞ってもう少し掘り下げてみる。

APIの認証をやらなくなった現状


 Google Data APIには以前、Client Login方式 (https://developers.google.com/accounts/docs/AuthForInstalledApps)というのがあったが今は廃止されてOAuth2.0に移行している。 

かつてのClientLogin方式はID/PW認証ができてとても便利だったのを覚えている。しかし、しばしばCAPTCHAロックが発生するため、アプリでID/PW固定で使うのは困難であった。結局、Googleは不正ログイン対策のためID/PW方式を諦めざるを得なかったのだと思う。(廃止理由:http://adwords-ja.blogspot.jp/2013/09/adwords-api-clientlogin.html) 

GoogleのClientLoginの件もしかり、昔流行ったWSSEにしても、 https://www.google.co.jp/search?q=WSSE で検索してもわかるように、多くのサービスがOAuth2.0に移行している現状がある。これは、リピート攻撃に弱くて筋が悪いと言われているWSSEを廃止したいことや、Webの認証機能に一元化したいという背景があるように思う。また、前述したような、ユーザがどこで認証するかを正しく認識する必要があることや、認証と認可を分けて考えればそもそもAPIに認証が不要だったというケースも多いのかもしれない。

でも、よく考えてみると、WebのログインフォームからはID/PWが送信されているわけで、API認証を廃止したところでログインフォームの動作をAPIでエミュレートすることが可能である。むしろそれを禁止する方が難しく、例えば、ログインページをスクレイピングしてtokenを切り取るなどすれば大概はエミュレートできる。(ただ、ワンタイムトークンなど発行することで難しくすることはできる) 

ブラウザでできることをプログラムから実行するのがなぜまずいのか。 今のところ、ログインフォームのようにID/PWをうまく保存する方法がないとか、不正ログイン対策のためのCAPTCHAをプログラムでハンドルするのが難しい、ぐらいしか説明が見つからない。

ログインフォームなどのセキュリティ強化策


 ID/PWを送信するAPI認証がセキュリティ上まずいとはいったものの、そもそもログインフォームのエミュレートを防げないのであればAPI認証の非公開にかかわらず意味がないし、結局はログインフォーム自体のセキュリティを強化する必要があるように思う。 

例えば、生PWを送信するリスクをどう考えるか。 ブラウザのログインフォームにしてもAPIにしても認証を行う際には必ずID/PWを必要とするが、生のPWを送信するときには細心の注意が必要である。(かつて、URLパラメータにPWを含めてしまうとRefererに機能によってリンク先に送出されることがあったように) 
またSSLで暗号化していてもログに書かれてしまうことで漏洩の危険性が高まるため可能であれば生で送信はやめた方がよいと思う。 

そこで、Basic認証は使わずにDigest認証やWSSEのようにパスワードをハッシュ化(※2)することでパスワードそのものの推定を難しくすることを考えてみる。これで万一漏洩した際にも同じパスワード使ってるサービスへの影響が少なくなるというメリットが生じる。

 個人的には、(今さらかよ!と思うかもしれんが)WSSEをハッシュ化して送信する方法で十分だと考えている。ただし、サーバではリピート攻撃に耐えうるようにWSSEをワンタイム化する。 そのまま保存してしまうとハッシュ化パスが事実上のパスワードになってしまうため、PWをさらにハッシュ化(または暗号化)したものを保存する。 生パスワードを知らないので漏れることも悪用(生パスワードを保存しておいて、他のシステムの認証にも使ってしまうようなこと)される心配もないためユーザに対して安心感は増すと考えられる。 また、初回だけWSSEを送って認証に成功したらサービス側で有効なトークン(セッションID)を発行してクライアントは以後毎回それを送るというブラウザでのセッションクッキーと同等の仕組みにすると毎回パスワードを入力しなくてもよくなるのでユーザビリティが増す。

これを実装するのに、Javascriptでパスワードをハッシュ化してXHRで送信することになると思うが、CSRF対策も考える必要がある。 
http://matome.naver.jp/odai/2133794394551702001 
http://utf-8.jp/public/20131114/owasp.pptx (P24-25に対策が載っている) 

不正ログイン対策まで考えるのであれば、CAPTCHAの他にリスクベース認証や二要素認証などの防御対策の他、不正アクセスがないかチェックするためのログイン履歴機能なども必要になってくるだろう。つまり、API認証だけのセキュリティを考えるのではなく、ログインフォームを含む認証全体の機能についてよく考えないといけないという話になる。

APIKey+WSSE


 最後に、どうしてもAPIで認証したいという場合の簡易的な方法について思いついたことを紹介する。 

これは正規のIdpからお墨付き(APIKey)をもらうことでログイン認証できる権限を得る方法である。 
具体的には、WSSEのこれまでのPasswordDigest生成ロジックを改良したもので、管理画面から入手したapikeyを先頭にくっつけてsha256にしてbase64エンコードするだけ。(注:元のWSSEはsha1だった。WSSEはワンタイムである必要がある) 
 PasswordDigest = base64(sha256(APIKey.base64_decode(Nonce).Created.Password)) 
 ID/PW以外にsecretのAPIkeyを付けることで2要素認証的な意味でセキュリティを強化できる。 また、アプリごとにトークンを使い分けることや、(不正ログインなどの)問題が発生した場合にキーを無効にできたり、より柔軟な運用が可能になる。複数回ログイン失敗で検知できる仕組みは必要だが、CAPTCHAなどのロックの仕組みは必要ないだろう。

 ただ、このようなオレオレ認証にはリスクが伴う。 
snapchatの事件(http://www.thread-safe.com/2014/01/46m-usernames-phone-numbers-leaked-by.html) も起きたばかりなので慎重に考えていきたいところである。


(2016/1/6 追記) BaaS vte.cxでは結局、こんな感じで整理した。=> セキュリティについて

(※1) もしクローズドなHTTPクライアントだとしたらどうやってSSLチェックしてホスト証明したと示せるのかが問題になる。まずはクライアント自体が信頼できるものであることを証明できないといけない。詳しくはこちらの資料(http://www.slideshare.net/shunsuketaniguchi520/ssl-28267938)のP7-を参照。適切なサーバ証明書の設定により接続先のサーバの正当性は検証できるが、スマホアプリにはアドレスバーがないものが多く、利用者が能動的に接続先や接続プロトコル(https)を確かめるのが困難である。

(※2) Saltが無いハッシュでは生パスワードが推測可能になるという危険があります。sha256(APIkey+nonce+日時+パスワード)のnonceがソルトに相当します。さらにDBに保存するときにストレッティングして保存、または暗号化します。

(追記)
@ritouさんからご意見いただいた。感謝です。



ごもっとも。
ちょっと、Resource Owner Password Credentialsについて調べてみます。

https://github.com/applicake/doorkeeper/wiki/Using-Resource-Owner-Password-Credentials-flow によれば、

以下のリクエストをOAuth Providerに投げると、

{
  "grant_type"    : "password",
  "username"      : "user@example.com",
  "password"      : "sekret",
  "client_id"     : "the_client_id",
  "client_secret" : "the_client_secret"
}

以下のように、access_tokenが返ってくるので、

{
  "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
  "token_type": "bearer",
  "expires_in": 7200
}

あとは、Authorizationヘッダに"Bearer " + access_token
をAPI実行時のヘッダに付ければいけます。

PS. しかし、これはPWが生で送られている。パスワードを送信してたらログに書くとかろくでもないことをする人が出てくるという経験則がある。
オレオレ認証作ったやつには税金かけろみたいな話( http://togetter.com/li/617280 )が聞こえてきて(;゜Д゜)ガクブル だしなあ。困った。

 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。