Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rails Tutorial 第9章 #71

Closed
kenta0425 opened this issue Jan 28, 2021 · 1 comment
Closed

Rails Tutorial 第9章 #71

kenta0425 opened this issue Jan 28, 2021 · 1 comment
Assignees

Comments

@kenta0425
Copy link
Collaborator

WHY

WHAT

  • 第9章の発展的なログイン機構を理解し完成すること
@kenta0425
Copy link
Collaborator Author

kenta0425 commented Jan 28, 2021

発展的なログイン機構

セッションの永続化

  • remember token(記憶トークン)を生成する
  • cookiesメソッドによる永続的cookiesの作成や、安全性の高い(remember digest)記憶ダイジェストによるトークン認証にこの認証トークンを活用する

cookiesを盗み出す主な方法

  • ネットワークに流れるパケットを解析して盗む
    • SSL(HTTPS)を使うことで暗号化にして対策
  • データベースから記憶トークンを取り出す
    • トークンは必ずハッシュ化して対策
  • クロスサイトスプリング(XSS)を使う
    • CSRFトークンを毎回は発行し、同一性を確認して対策
  • ユーザーがログインしているパソコンやスマホを直接操作してアクセスを奪い取る
    • ログアウトしたら既存のトークンを無効化して対策

ハッシュ化

  • 元の状態には戻せない状態にする

暗号化

  • 復号できる(元の状態に戻せる)こと

永続的セッションの流れ

  • 記憶トークンにはランダムな文字列を生成して用いる
  • ブラウザのcookiesにトークンを保存するときには、有効期限を設定する
  • トークンはハッシュ値に変換してからデータベースに保存する
  • ブラウザのcookiesに保存するユーザーIDは暗号化しておく
  • 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する

永続的セッションの実装

  1. 記憶トークンを保存するためのカラム (remember_digest) を作成
  2. ランダムな文字列を生成し、ハッシュ化する
  • 記憶トークン(User.new_token)を作るする
  • Ruby標準ライブラリのSecureRandomモジュール(クラス)にあるurlsafe_base64メソッド (A-Z、a-z、0-9、"-"、"_"のいずれかの文字(64種類)からなる長さ22のランダムな文字列を返す) を使う
def User.new_token
  SecureRandom.urlsafe_base64
end
  1. ハッシュ化した値を、DBのカラム (remember_digest) に保存する
  • remember (記憶トークンをユーザーと関連付け、記憶トークンに対応する記憶ダイジェストをDBに保存するメソッド) を作成する
  • update_attribute (バリデーションを走らせずに更新するメソッド)
def remember
  self.remember_token = User.new_token
  update_attribute(:remember_digest, 
                User.digest(remember_token))
end
  1. ブラウザのcookiesに、暗号化したIDとTokenを保存する
  • remember_token メソッドを使って記憶トークンにアクセスできるようにする
  • attr_accessor を使って仮装の属性を作成すれば、記憶トークンをデータベースに保存せずに実装できる
attr_accessor :remember_token
  • remember メソッド(引数あり)を作る
    • コントローラ内で使うヘルパーメソッドを作る
    • モデリ内で定義したrememberインスタンスメソッド使う
    • permanetメソッドは期限を20年に設定してくれる
    • signed は暗号化と復号化してくれる
def remember(user) # 引数に必ずuserオブジェクトを取らないといけない
  user.remember
  cookies.permanent.signed[:user_id] = user.id
  cookies.permanent[:remember_token] = user.remember_token
end
  • 引数に渡されたuserオブジェクトを使って、new_tokenを発行する
  • remeber_digestカラムを更新する
  • cookiesの中にuser_idをいれ、attr_accessorで準備したremember_tokenをいれる
  1. cookiesのIDを使って、ユーザーをDBから検索し、cookiesのTokenを認証し、同一ならセッション復元
  • authenticated?メソッドを作る
    • remember_digestがnilの場合returnキーワードで即座にメソッドを終了する
    • 渡されたトークン(remember_token)とDBのダイジェスト(remember_digest)が一致しているかBCryptが判断してくれるメソッド
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?
                      (remember_token)
end
  • 復元する仕組み
    • user_id にsessionの中にあるuser_idを引っ張ってきて、nilかどうか判断し、true (sessionにidがある) なら、2行目に移り、1回目ならfind_byでidを検索して復元、2回目以降ならすでにuserオブジェクトが@current_userに入っているため復元できる
    • nil の場合、cookiesの中にidが入ってるかチェックして入っていれば(true)、そのidを使ってfind_byでuserを引っ張ってきて、そのuserが true (存在する)なら authenticated? (引数はuserのcookiesにあるremember_token)を走らせて、trueなら復元できる
if (user_id = session[:user_id]) # 丸カッコはなくてもよい
  @current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])  # 同上
  user = User.find_by(id: user_id)
  if user && user.authenticated?(cookies[:remember_token])
    log_in user
    @current_user = user
  end
end

※ 右辺に左辺を代入した結果(trueかnil)をチェックしている

if (user_id = session[:user_id])

ログアウト時にセッションを消す

  • nil を渡してユーザーのログイン情報を破棄するforgetメソッドを作成する
def forget
  update_attribute(:remember_digest, nil)
end
  • 引数ありのヘルパーメソッドを作る
    • cookiesの中のuser_idremember_tokenを削除する
def forget(user)
  user.forget
  cookies.delete(:user_id)
  cookies.delete(:remember_token)
end
  • ログアウト時にforget()メソッドが使われるように記述する

チェックボックス

  • BootstrapのCSS定義でクラスが定義されているから、同じ行に配置する為に、CSSクラスにcheckbox と inline と記述する
.d-inline {
  display: inline !important;
}
<%= f.label :remember_me, class: "checkbox inline" do %>
  • チェックボックスがオンのときに 1 になり、オフの時 0 になる
if params[:session][:remember_me] == '1'
  remember(user)
else
  forget(user)
end

↓ 三項演算子を使って一行で表せる

params[:session][:remember_me] == '1' ? remember(user) : forget(user)

log_in_as ヘルパーメソッド(テスト用)

  • test_helperファイルのActionDispatch::IntegrationTestクラス内に定義する
    -- sessionメソッドを直接操作して、:user_idキーにuser.idの値を代入する
def log_in_as(user)
  session[:user_id] = user.id
end

This was referenced Jan 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant