#はじめに
Webアプリに限らず、ほとんどのサービスではユーザ名とパスワードを入力し、ログインをしてから
サービスを使う
OSのログインしかり、メールアプリ、Webサービスなど
ユーザ名とパスワードの入力という基本部分はほぼ変わってないが
時代とともにより安全な仕組みへと進化しているので
私の知る限りの説明したいとおもう
#認証、認可の違い
まずややこしいのが、認証、認可(承認)のちがいである
認証(Authentication) は、ユーザーが正しいかどうかの確認(パスワード認証)で
認可、承認(Authorization)は、アクセス権の確認である
昔はこの認証、認可を同時に行っていたが、OAuth等の登場から厳密に分ける必要が出てきた
ただ、いまだにHTTPヘッダでも混同して使われていたりする
#歴史
ただし順番に並んでいるともかぎらないし、抜けもある
##平文ベーシック認証
いわゆるBasic認証と称して、ユーザ名とパスワードが平文で送られていた
今考えると恐ろしい世界だ
悪意ある人が一度その情報を取得すると、簡単に乗っ取ることが可能
また、パスワードは他のサイトでも使い回す事が多いので、下手すると全てのサービスが乗っ取られる
初期の頃は、フォームからPOSTなどで平文パスワードをログイン時に送っていた
(なかにはGETでクエリパラメータでURLにパスワードが平文でのってる恐ろしいサービスもあった)
Postで送らない方法もある。たとえばHTTPヘッダに乗せるなど
また、TextではなくJson等で送る方法もある
が、平文であればいずれも問題である
##ハッシュ
平文でパスワードを送ると盗まれると危険である
そのためパスワードをHashし、それを送ることで平文よりは安全になる
Hashとは、不可逆な一方向変換である
https://qiita.com/chroju/items/3ddae568206b8bc3d8f9
これで、パスワード自体が取られる事はなくなった
passwordより5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8 の方がわかりにくいでしょ?
sha1やmd5のような暗号強度の低いものはレインボーテーブルという、Hashの逆引きデータが販売されている
レインボーテーブル
https://dev.classmethod.jp/security/rainbowtable/
ので、より強度な暗号アルゴリズムを使い、さらに ソルトやストレッチングを行い
暗号強度を増やすべきである
##ランダムソルト&ストレッチング
パスワードをハッシュ化したので、パスワードを知られる事はなくなった(解読されなければ)
しかし、ハッシュ値を知っていればなりすましログインも可能だし、同じハッシュのユーザは同じパスワードだとわかる
ゆえに、ユーザーごとに異なるSaltをつければ上記の問題がなくなり、安全性もあがる
Saltとはパスワードに追加する追加文字列である
また、Saltと同時にストレッチング(Hashの重ね掛け)を行うと強度が増す
上記処理には PHPの password_hash
関数などが有名
##セッション&クッキー
通信のたびにユーザ名とパスワードを送るのはハッキングを受ける確率も増えるし
毎回認証のオーバーヘッドは避けたい
そこでセッションを使う
はじめにサーバはクライアントにランダムな値のセッションIDを送る。
サーバはセッションIDと紐付ける事でクライアントの状態を保持できるため
認証が成功すれば、その後はセッションIDの確認だけで認証が不要になる
セッションIDは盗まれても、ワンタイムなのでそこまで痛くない。一定期間で無効になるし、パスワード情報が何もない
また、セッションには認証だけでなく、それまでのやり取りのステート(例えばショッピングカートにアイテムを入れる)も保存することが可能
https://qiita.com/7968/items/ce03feb17c8eaa6e4672
###セッションハイジャック
URLパラメータにセッションIDを入れると簡単にセッションを乗っ取られる
セッションIDは Cookieに保存することで、簡単かつ比較的安全にセッション管理が出来る
(もちろんCookieを盗んだりも可能であるが、URLパラメータに表示するよりは圧倒的にましである)
###セッション・フィクセーション
攻撃者が有効なセッションIDを対象のCookieに送り込んで、以後そのセッションIDが対象とひも付き
攻撃者が自由に乗っ取れる
これは、セッションIDがユーザ固有ではなく、セッションIDがランダムでかわらないサイトだとおこりうるが
ユーザごとにセッションIDを発行していればおきない
###CSRF(Cross Site Request Forgery)
https://qiita.com/maruloop/items/e14d02299bd136f4b1fc
ログインとは少し話がずれるが、気をつける必要がある
何かしらの方法で悪意のあるURLやJavaScriptを踏ませる
具体的にはimageタグやメール等をクリックさせる
そうすると、偶然裏でサービスにログインしていた場合、悪意のあるURLが正規呼び出しだと勘違いされ
実行されてしまう
例えばブログへの書き込みだったり、銀行振り込みかもしれない
単純な攻撃ではあるが、防ぐには少し厄介だ
Referで判定できればいいのだが、そうもいかない
そこで最も有効だと思われるものに
Hiddenにワンタイムトークンを仕込む事である
トークン(CSRFトークンと呼ぶ)を識別することで、認証を行ったページからのアクセスを判断可能
c-yanさんの指摘で、Hiddenではなくヘッダに入れる実装もあるとのことですが、そのあたり他の要素もからみ纏められなかったのでリンクにて。
https://www.scutum.jp/information/waf_tech_blog/2017/11/waf-blog-051.html
##Authorizationヘッダ
Webページの場合、セッションをCookieに入れると良いとかいたが
WebAPIではCookieが使えないため、Cookieに依存すべきではない
その場合はAuthorizationヘッダを使うべきだ
間違ってもURLにクエリパラメータとして置いてはいけない。
下記のHTTPSで保護しても無駄になるし
##HTTPS
TLSレイヤーにて暗号化を行っている
設定によっては脆弱性があるが非常に強力で
基本的にはHTTPSを使えばBasic認証で送ってもかなり安全ではある
ただし、古いバージョンは脆弱性があるので注意
下記のまとめは個人的に有用だと思った
https://qiita.com/mpyw/items/bb8305ba196f5105be15
##OAuth、OpenID
色々あり今使われているのは
OAuth2とOpenID Connect
簡単にいうと、OpenID Connectは、OAuth2 + User情報取得 である。
だが実際はFacebook等のOAuth2のサーバでもUser情報は取得している(/meへリダイレクト)
やはり認証と認可をセットにすることが多い。
つまり、OpenID Connectだと、取得ページへのリダイレクトがないぶん効率良く認証が出来る
どちらも肝の部分は
認証サーバ(たとえばFacebookやTwitter)を使い、認証サーバにログインし、権限のトークンをもらう(認可)
そのトークンの権限を使い、認証サーバの操作が可能になる。
たとえばFacebookのウォールの読み書きをしたいなら、読み書き許可のあるトークンを使いFacebookにアクセスする
ここで素晴らしいのは、ログイン(認証)はFacebookにFacebookで行う事ができるため
自サーバにFacebookのログイン情報を保持しなくてよい
##JWT
Json Web Token
OpenIDのトークンで使われる
https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06
OAuth2でも使われていると思ったが、アクセストークンは定義されてないっぽい??
JWSで出来る事は、改ざん防止だ(暗号化はない)
仕組み的には、ヘッダ、ペイロード、署名から成り立つ
署名にはメッセージのハッシュ値が入っているので、改ざんすることは不可能だ
ただし、暗号化されていないので、だれでも内容を見ることはできる
一般的にはJWSのペイロードに ユーザIDとトークンの有効期間を置く
JWSは署名により改ざん不可能なため、このユーザしか使えない、期間限定トークンだ
セッションIDに近いが少し違う
セッションIDはランダムな値なため、サーバ上のDBと照らし合わせて判定が必要だ
Webサーバがスケールした時、異なるサーバへ割り当てられた場合セッションの照合が出来ない
(そのため、複数のWebサーバで運用するには DBなりKVSでのセッション共有必要)
ところがJWSの場合は、サーバの秘密鍵で署名の有効性を確認できるので
KVSもDBも不要です(DBについては後述・・)
JWEは暗号化も含めた規格だが、あまりドキュメントがないし
HTTPSがあれば大丈夫(とか思ってはいかんのだろうか?)
###JWTの危険性
前に仕事で、JWTは危険だという記事を見せられこれについてどう思うか?と言われた
JOSEというのは、JWTの中の署名(JWS)と暗号化(JWE)の総称である
一般的にJWTといえばJWSの事だと思っていい
JOSEの脆弱性は、暗号化方式を noneやHMAC(共有鍵方式)
に書き換えて改ざんが出来ることだ
大抵のフレームワークでは既に対応されてると思いたいが
暗号化方式の noneとHMACは弾くようにすべきだ
(これらがアルゴリズムと設定出来る事が脆弱性だ)
なので、上記を気をつければ、積極的に使うべきだと思う
JWTはBase64urlエンコードされているためURLセーフだ
OAuth等でよく使われる bearerトークンは Token68を満たす必要があるが
Base64(url)はToken68要件を満たすため、JWTトークンはそのままbearerトークンとして送る事ができる
HTTPヘッダに
Authorization: bearer
と送ってしまおう
##JWTのsalt
上記、JWSを使えばセッションIDのようにDBを参照する必要がない
と言ったが、ちょっとウソでした
改ざん防止してあるので、DBなしで認証出来るが
同じトークンを使い回す事が可能である
(有効期間をつければ、一定時間で無効にはなるが)
よりセキュアに、トークンを1回で使えなくするために
ペイロードにユーザ事、通信のたびに変動する乱数のSaltを入れるべきである
結果として、SaltをユーザごとにDB管理する必要がある
#まとめ
HTTPSを使い
パスワードは十分にセキュアなHash方式を採用し
ユーザーごとに異なるSalt付与やストレッチングを行なう
DBにはHashされたパスワードを保存し、ログイン時に認証を行なう
その後の通信は、SaltつきのJWT等でワンタイムトークンにて認証する
HTTPSで保護されてるが、通信内容を暗号化したい場合はJWEなり別途暗号化を入れる
ぐらいしておけば、普通の用途には十分と思われる
暗号化とデータ改ざんは、基本的には強固になればCPU負荷が増えるため
セキュリティ要件とのトレードオフになる事が多い