これは何?
Railsを使って開発をしている人のほとんどが、
おそらく何かしらの認証機能を提供していると思いますが、
その認証機能って初心者の人からすると非常にややこしい。
確かに、Railsが色々やり易くしてくれてるのはわかるけれども、
逆にブラックボックスすぎて全然意味わからんし!みたいなところもあるかと思います。
また、認証はセキュリティを高めるために非常に重要なトピックでもあり、開発者で担当する人が深く理解しておくことが求められると思います。
ただ、認証はアプリやインフラから独立しているものではないため、認証だけよくても他の部分に脆弱性があれば危険であることは変わらない点に注意してください。
そもそも認証ってなんぞ
認証は、アクセス制限機能と思ってもらえれば大丈夫そうです。
アクセスを制御したい場合に認証機能を利用して閲覧ページなどに制限をかける感じです。
認証の種類
Railsで開発をして行く上では、3種類の認証タイプがあります。
- Basic認証
- Digest認証
- Token認証
こちらでも公式で三つのモジュールが公開されているのでこれさえ知っておけば十分だと思います。
Basic認証
その名の通りですが、もっとも基本的な認証方法です。
ユーザー名とパスワードをBase64と呼ばれる変換方式を用いたデータとして送信して、アクセス制御をする方法です。
セキュリティを意識する状況で利用するには心細い認証機能のため、基本的にはSSLやローカル上で利用する程度に収めておくようにした方が安全のようです。
- クライアントから認証ページにアクセス(クライアントは認証ページだと知らない)
- 認証ページを表示するサーバーはクライアントに401(認証エラー)のリクエストを返す
- クライアントからユーザーIDとパスワードをコロンで連結した文字列をBase64でエンコードしたリクエストが送られる
- リクエストの内容が合致するかどうかを調べる
要は、3の場面でユーザー名とパスワードなど知られたら危険な文字列をそのまま平文で暗号化してしまうので、
デコードされてしまえばすぐに暗号が説かれてしまうということで実質暗号化してないよねってことで危険です。
Sample Code
class PostsController < ApplicationController
http_basic_authenticate_with name:"dhh", password:"secret", except: :index
def index
render plain: "誰でも閲覧可能"
end
def edit
render plain: "認証した人だけ閲覧可能"
end
end
応用編はこちらにもあります。
(僕はまだ全然理解できてません・・)
Digest認証
ProgateやRailsTutorialなどをやった方はPassword_digestカラムみたいなのを作成してパスワードを暗号化したのを覚えてると思うのですが、そのDigestと同じです。
Basic認証と同様にユーザー名とパスワードが必要なんですが、パスワードをハッシュ関数MD5を用いて解析されにくい状態で送信する仕組みです。
Basic認証がパスワードをそのまま平文で暗号処理していたのに対してDigest認証はパスワードにランダム文字列を組み合わせたものを暗号化して送信します。
- クライアントから認証ページにアクセス(クライアントは認証ページだと知らない)
- 認証ページを表示するサーバーはクライアントに401(認証エラー)のリクエストを返す
- この時にリクエストごとに生成されるランダム文字列(nonce)も一緒に返されます
-
curl -I リクエスト名
とかやるとHTTP Headerを確認できてnonceがリクエストのたびに変わってるのが確認できます。
- クライアントからユーザー名は平文で、パスワードはnonceと入力値をコネコネしてMD5でハッシュ化してから送信されます。
- リクエストの内容が合致するかどうかを調べる。
require 'digest/md5'
class PostsController < ApplicationController
REALM = "SuperSecret"
USERS = {"dhh" => "secret", #plain text password
"dap" => Digest::MD5.hexdigest(["dap", REALM, "secret"].join(":"))}
before_action :authenticate, except: [:index]
def index
render plain: "誰でも閲覧可能"
end
def edit
render plain: "認証した人だけ閲覧可能"
end
private
def authenticate
authenticate_or_request_with_http_digest(REALM) do |username|
USERS[username]
end
end
end
authenticate
メソッドの中のusername
はダイアログで入力してもらった「ユーザー名」のことで、
このメソッドでの返り値はパスワードになる必要があります。
その返り値のパスワードとダイアログで入力してもらったパスワードが同一であれば認証が通ります。
Token認証
認証ライブラリ
- Devise
- Authlogic
- sorcery
- Clearance
- Rodauth
- OmniAuth
認証ライブラリはこの他にもたくさんありますが、代表的なものだけピックアップして少し見てみます。
Devise
Railsでおそらく最も人気な認証機能を実装できるgemです。
上で見てきたように、認証機能の実装には理解に時間がかかりますが、Deviseを利用すれば簡単に安全な認証が作れます。
gem 'devise'
gemをインストールした後は、基本的に三つのコマンドだけで終了します。
bundle exec rails generate devise:install
=> Infoが出てくると思いますので従って設定してください。
bundle exec rails generate devise モデル名(だいたいUserになるかと)
=> 作成されたモデルとマイグレーションファイルに、認証機能に必要なモジュールが含まれてない場合は追加してください。
※ omniauthable
というSNS認証などに使うモジュールについては追加のgemが必要なのでご注意ください。
bundle exec rails db:migrate
あとはサーバーを立ち上げてlocalhost:3000/users/sign_up
にアクセスすると新規作成画面が出て来ます。
見た中で一番詳しかったのはこちら。
rack-attack
アプリケーション側でのリクエスト制御用gemです。
単一IPからの制御例
下記のように記載することで、5分間に300以上のリクエストを送信するすべてのIPアドレスを制限するようにRack::Attackに伝えることができます。
throttle('req/ip', :limit => 300, :period => 5.minutes) do |req|
req.ip
end
複数IPからの制御例
throttle("logins/email", :limit => 5, :period => 20.seconds) do |req|
if req.path == '/login' && req.post?
#
req.params['email'].presence
end
end
こちらの記事をほとんど参照させてもらいました。
不正アクセスが多い場合はアカウントをバンすればいいと思うかもしれないですが、逆手にとってDoS攻撃の一種でアカウントをバンにする攻撃もあるのでここらへんは慎重になるべきとのことです。
has_secure_password
gemを使わないで、カスタマイズされたログイン機能作りたいぜ!って人はこちらを使ってください。
gem 'bcrypt'
bundle install
bundle exec rails g controller Admins index new create
bundle exec rails g model Admin name:string email:string password_digest:string
bundle exec rails db:migrate
上記のようにhas_secure_password
で生成される暗号化されたパスワードを格納するpassword_digest
というカラムを用意する必要があります。
実際には仮想カラムとしてpassword
とpassword_confirmation
が作成されています。
※これらのプロパティはDBではなくてメモリ上で管理されます
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
=> こんな感じに必要に応じてvalidatesもかけておきます。
def new
@admin = Admin.new
end
def create
@admin = Admin.new(admin_params)
@admin.save
redirect_to new_admin_path
end
private
def admin_params
params.require(:admin).permit(:name,:email,:password,:password_confirmation)
end
=> form_with使うために
<%= form_with model:@admin do |f| %>
...
<%= f.password_field :password %>
<%= f.password_field :password_confirmation %>
...
<% end %>
=>こんな感じにpasswordプロパティを使うことができる。
試しに保存してみると、DBのpassword_digest
に暗号化されたパスワードが保存されているのが確認できると思います。
config.force_ssl = true
アクセスをhttpsにする方法です。くそ簡単です。
config.force_ssl = true
#まとめ
認証についての基礎知識を書くだけのつもりでしたが、かなりもりもりになってしまいました。
ここに書いてある内容は認証という領域のほんの一部ですが、これだけ理解するだけでも認証の難しさがわかるかと思います。
実運用する場合は例えばSSL通信にしなくても、
設計によってはロードバランサーとかがSSLの受け皿になってたりもするので上記のような設定をしなくてもセキュアになってることもあります。
どういった設計でセキュアな状態を保つのかは非常に面白いトピックでもあり重要でもある領域なので僕もどんどん学んでは発信したいと思います。