實(shí)現(xiàn)登錄功能
注冊(cè)模塊一般用users
登陸模塊一般用sessions
- 配置登錄模塊路由
get '/login', to: 'sessions#new'
post '/login', to:'sessions#create'
delete '/logout', to: 'sessions#destroy’
- 準(zhǔn)備登錄表單頁(yè)面
- 編寫(xiě)控制器(sessions_controller.rb)
控制器中的邏輯(sessions_controller.rb)
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in(user)
#判斷是否要記住密碼
params[:session][:remember_me] == '1'? remember(user) : forget(user)
redirect_to users_path
else
#flash.now 與flash不同,專門(mén)用于在重新渲染的(render)頁(yè)面中顯示閃現(xiàn)消息,會(huì)在下次請(qǐng)求時(shí)消失
flush.now[:error]='賬號(hào)或密碼錯(cuò)誤'
render 'new'
end
end
def destroy
sign_out
redirect_to new_session_path
end
end
4.編寫(xiě)用戶認(rèn)證需要的方法(sessions_helper.rb)
module SessionsHelper
#登錄操作
def sign_in(user)
#這么做會(huì)在用戶的瀏覽器中創(chuàng)建一個(gè)臨時(shí) cookie,內(nèi)容是加密后的用戶 ID。瀏覽器關(guān)閉就會(huì)被清除。在后續(xù)的請(qǐng)求中,可以使用 session[:user_id] 取回這個(gè) ID
session[:user_id] = user.id
end
#記住用戶,持久性存儲(chǔ)登錄信息(存cookie)
#1.創(chuàng)建記憶令牌把未加密的存儲(chǔ)到cookie把記憶令牌加密更新到數(shù)據(jù)庫(kù)
#2.把用戶設(shè)置為當(dāng)前登錄用戶
def remember(user)
remember_token = User.new_remember_token
user.remember(remember_token)
cookies.permanent[:remember_token] = remember_token #permanent自動(dòng)將過(guò)期時(shí)間設(shè)置為20年之后
cookies.permanent.signed[:user_id] = user.id #signed設(shè)置存入瀏覽器前安全加密cookie中的用戶ID
self.current_user = user
end
#忘記用戶,清空持久性登錄信息(清空cookie)
def forget(user)
user.forget #清空用戶的記憶令牌
cookies.delete(:remember_token)
cookies.delete(:user_id)
end
#登錄成功存儲(chǔ)當(dāng)前登錄用戶信息
def current_user = (user)
@current_user = user
end
#判斷是否登錄
#登錄用戶可能是從登錄頁(yè)面登錄進(jìn)來(lái),也有可能是記住密碼進(jìn)來(lái),所以如果@current_user沒(méi)值可以通過(guò)cookie獲取值
def sign_in?
!current_user.nil?
end
#獲取當(dāng)前登錄用戶
def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by_id(user_id)
else (user_id = cookies.signed(:user_id)) #解密cookie中的用戶ID
user = User.find_by_id(user_id)
#if user.remember_digest == User.encrypt(cookies[:remember_token])
if user && user.authenticated?(cookies[:remember_token])
sign_in user
@current_user = user
end
end
end
#退出登錄
def sign_out
#忘記持久會(huì)話
forget(current_user)
#忘記session
session.delete(:user_id)
@current_user = nil
end
end
5.編寫(xiě)model中的邏輯(user.model)
class User < ActiveRecord::Base
#attr_accessor: remember_token
#before_create :create_remember_token
#生成記憶令牌(安全隨機(jī)數(shù)),返回A-Z a-z 0-9 -_ 長(zhǎng)度為22的隨機(jī)字符串,每一位有64種可能
def self.new_remember_token
SecureRandom.urlsafe_base64
end
#加密算法
#def encrypt(token)
#Digest::SHA1.hexdigest(token.to_s)
#end
#返回指定字符串的哈希摘要(不可逆加密)
def self.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
#更新users表記憶令牌密文
def remember(remember_token)
user.update_attribute(:remember_digest,User.digest(remember_token))
end
#如果指定的令牌和摘要匹配,返回 true否則返回false
#remember_digest相當(dāng)于self.remember_digest
#remember_token只是變量
def authenticated?(remember_token)
return false if remember_token.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
def forget
self.update_attribute(:remember_token,nil)
end
private
#創(chuàng)建記憶令牌
#def create_remember_token
#self.remember_digest = User.digest(User.new_remember_token)
#end
end
關(guān)于為什么在記住密碼時(shí),已經(jīng)使用cookie.signed[:user_id] =user.id對(duì)cookie的用戶ID進(jìn)行加密了為什么還要使用記憶令牌的問(wèn)題
因?yàn)橐坏┇@取了加密的cookie攻擊者就可以冒充該用戶的身份進(jìn)行登錄,但是如果加了記憶令牌進(jìn)行多重驗(yàn)證,即使攻擊者獲取了用戶ID和記憶令牌進(jìn)行登錄,但是因?yàn)槊看蔚卿浂紩?huì)更改記憶令牌,退出會(huì)清空記憶令牌,所以冒充者最多只能維持登錄狀態(tài)到真正的用戶退出
authenticated?方法
在 User 模型中定義的 authenticated? 方法,比較摘要(加密令牌)和令牌。這個(gè)方法的作用類似于注冊(cè)模塊中 has_secure_password 提供的用來(lái)認(rèn)證用戶的 authenticate 方法
注:參考了很多資料,發(fā)現(xiàn)在實(shí)現(xiàn)存儲(chǔ)記憶令牌時(shí),都是選擇增加字段remember_digest存儲(chǔ)記憶令牌密文,并且定義一個(gè)虛擬屬性remember_token存儲(chǔ)生成的記憶令牌明文,但是我覺(jué)著沒(méi)必要新增一個(gè)虛擬屬性直接用變量代替了,所以在本例中只增加了一個(gè)字段remember_digest,(差異主要體現(xiàn)在helper模塊中的remember方法中,如果使用虛擬屬性還要在model中聲明attr_accessor: remember_token),如后續(xù)發(fā)現(xiàn)弊端會(huì)及時(shí)更新
參考:rails tutorial