Edited at

ユーザログイン(ユーザ認証)の歴史


はじめに

Webアプリに限らず、ほとんどのサービスではユーザ名とパスワードを入力し、ログインをしてから

サービスを使う

OSのログインしかり、メールアプリ、Webサービスなど

ユーザ名とパスワードの入力という基本部分はほぼ変わってないが

時代とともにより安全な仕組みへと進化しているので

私の知る限りの説明したいとおもう


認証、認可の違い

まずややこしいのが、認証、認可(承認)のちがいである

認証(Authentication) は、ユーザーが正しいかどうかの確認(パスワード認証)で

認可、承認(Authorization)は、アクセス権の確認である

https://garafu.blogspot.com/2013/11/authentication-authorization.html

昔はこの認証、認可を同時に行っていたが、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ヘッダを使うべきだ

https://qiita.com/hirohero/items/d74bc04e16e6d05d2a4a

間違ってもURLにクエリパラメータとして置いてはいけない。

下記のHTTPSで保護しても無駄になるし


HTTPS

TLSレイヤーにて暗号化を行っている

設定によっては脆弱性があるが非常に強力で

基本的にはHTTPSを使えばBasic認証で送ってもかなり安全ではある

https://qiita.com/kuni-nakaji/items/5118b23bf2ea44fed96e

ただし、古いバージョンは脆弱性があるので注意

下記のまとめは個人的に有用だと思った

https://qiita.com/mpyw/items/bb8305ba196f5105be15


OAuth、OpenID

色々あり今使われているのは

OAuth2とOpenID Connect

https://www.buildinsider.net/enterprise/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でも使われていると思ったが、アクセストークンは定義されてないっぽい??

https://webbibouroku.com/Blog/Article/jwt

JWSで出来る事は、改ざん防止だ(暗号化はない)

仕組み的には、ヘッダ、ペイロード、署名から成り立つ

署名にはメッセージのハッシュ値が入っているので、改ざんすることは不可能だ

ただし、暗号化されていないので、だれでも内容を見ることはできる

一般的にはJWSのペイロードに ユーザIDとトークンの有効期間を置く

JWSは署名により改ざん不可能なため、このユーザしか使えない、期間限定トークンだ

セッションIDに近いが少し違う

セッションIDはランダムな値なため、サーバ上のDBと照らし合わせて判定が必要だ

Webサーバがスケールした時、異なるサーバへ割り当てられた場合セッションの照合が出来ない

(そのため、複数のWebサーバで運用するには DBなりKVSでのセッション共有必要)

ところがJWSの場合は、サーバの秘密鍵で署名の有効性を確認できるので

KVSもDBも不要です(DBについては後述・・)

JWEは暗号化も含めた規格だが、あまりドキュメントがないし

HTTPSがあれば大丈夫(とか思ってはいかんのだろうか?)


JWTの危険性

前に仕事で、JWTは危険だという記事を見せられこれについてどう思うか?と言われた

https://postd.cc/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid/

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負荷が増えるため

セキュリティ要件とのトレードオフになる事が多い