2020/12/15 railsチュートリアル9章
今回やること
cookiesを使ってセッション情報を保存する
データベースとブラウザを使ってやりとりする
ブラウザに暗号化したデータを保存(cookies)
cookiesのパスワードとデータベースのデータが一致するかを確認する
これまでは自分の覚えているパスワードがデータベースと一致しているかを確認していた
具体的に
・記憶トークン用のカラムを用意
・ランダムな文字列を生成しハッシュ化する(token)
・ハッシュ化した値をDBに保存する(remember_digestに保存)
・ブラウザのcookiesに暗号化したIDとTokenを保存する
・cookiesのIDを使ってユーザーをDBから検索
・tokenを認証し同一ならセッションを復元する
ランダムな文字列を生成しハッシュ化する(token)
SecureRandom.urlsafe_base64
ランダムな文字列を作ってくれる
これを使ってメソッドを作成
仮想的な属性を作る
has_secure_passwordを使ってパスワードを保存するとき、passwordカラムはないのにpassword属性を扱うことができた
ユーザーがpasswordを入力→authenticateでハッシュ化される→DBのpassword_digestと一致するか確認して認証
同じように仮想の属性を作りたい
attr_accessor :remember_token
クラスの中ではインスタンス変数と同じようにデータを保存できるけど、DBとは連携しない
ハッシュ化した値をDBに保存する→cookiesのIDを使ってユーザーをDBから検索
def User.new_token SecureRandom.urlsafe_base64 end def remember self.remember_token = User.new_token #ここでのselfはrememberメソッドのレシーバー(userインスタンス)のこと update_attribute(:remember_digest,User.digest(remember_token)) end
remember_tokenにランダムな文字列を保存、User.digestメソッドでハッシュ化して、update_attributeでremember_digestに保存
ブラウザのcookiesに暗号化したIDとTokenを保存する
cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc }
cookiesには、値(:remember_token)と期限(expires: 20.years.from_now.utc)を入れないといけない。
cookies.permanent[:remember_token] = remember_token
で書き込むこともできる。
IDは
cookies.permanent.signed[:user_id] = user.id
signedで暗号化できる。
rememberメソッドでremember_digestにキーを保存
cookiesメソッドでcookiesにキー(暗号化ずみ、ハッシュ化していない)を保存
というメソッドを作る
def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end
このメソッドをログイン時に使うことでDB、ブラウザにユーザーごとの認証データを保存することができる
tokenを認証し同一ならセッションを復元する
def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end
全然わからない
実際に使うときのコード見ると
user.authenticated?(cookies[:remember_token])
これ見てちょっとわかった気になる
このメソッドのremember_tokenは引数としてメソッドを使うときに渡すもので、さっきのattr_accessorの奴は別物
remember_digestは、レシーバーのDBにあるやつ
まだわからないので実験
# passwordは"chihuahua" irb(main):001:0> d = User.first.password_digest #変数dに代入 (0.4ms) SELECT sqlite_version(*) User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => "$2a$12$MQCAWCJf52lLohOv.GkyyOhga5dL.1P0oxst1.9BRHWJv4c6H29Ly" irb(main):002:0> d == "chihuahua" #もちろん一致しない => false irb(main):004:0> d2 = BCrypt::Password.new(d) #謎メソッドを実行 => "$2a$12$MQCAWCJf52lLohOv.GkyyOhga5dL.1P0oxst1.9BRHWJv4c6H29Ly" #ぱっと見変わらなそう irb(main):005:0> d == d2 #メソッド実行前と実行後は一致 => true irb(main):006:0> d2 == "chihuahua" #なぜか一致! => true irb(main):007:0> d == "chihuahua" #d==d2はtrueなのにこれはfalse => false
結果メソッドが何してるのか全然わからなかったけど、
とりあえずBCrypt::Password.newしたdigestの値は平文と比較できるんだってことだけ理解。
# 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) #セッション情報があれば @current_user ||= User.find_by(id: user_id) #@ccurent_userはセッションに情報がある人 elsif (user_id = cookies.signed[:user_id]) #セッション情報がなければcookiesに情報があるか、cookiesメソッドで持ってくる user = User.find_by(id: user_id) #cookies.signed[:user_id]のユーザーを代入 if user && user.authenticated?(cookies[:remember_token]) #ユーザーがいて、remember_digestの情報がただしければ log_in user @current_user = user end end end
if (user_id = session[:user_id])
=>比較ではなく代入、user_idに値があるかないかでtrue/falseを返す