一,相關資料
慣例,先上文檔,BCrypt::Engine主要用於生成隨機字符串,hash值。
二,內容及思路
最近的項目需要引進用戶管理,主要功能有:
- 註冊新用戶
- 修改密碼
- 登錄驗證
思路:
數據庫user表結構:
整體流程:核心內容,看這個就夠了數據庫裏存了encrypted_password,salt兩個關鍵的項。用戶註冊的時候,先隨機生成salt值,再根據用戶輸入的郵箱(或名字)與salt進行哈希運算,得到encrypted_password,存進數據庫裏。在用戶登錄的時候,根據用戶輸入的email(或名字)在數據庫中查詢,取得對應的user,將用戶輸入的password與數據庫裏的user.salt進行同樣的哈希運算,將得到的結果與數據庫中存儲的user.encrypted_password進行比較,如果相等,證明賬號密碼正確,允許登錄。成功登錄後,將數據庫中的user.name與user.salt進行哈希運算,得出user_token存成cookie[“user_token”],同時存儲cookie[“user_id”]。下次登錄時嘗試讀取該cookie,取得user_id,根據user_id獲取數據庫中對應的user,user.name與user.salt進行哈希運算,將得到的結果與cookie[“user_token”]比較,如果相同,則登錄成功。
三,用戶註冊
引用上文:用戶註冊的時候,先隨機生成salt值,再根據用戶輸入的郵箱(或名字)與salt進行哈希運算,得到encrypted_password,存進數據庫裏。
不多說,看代碼
def encrypt_password
if password.present?
#生成salt
self.salt = BCrypt::Engine.generate_salt if self.salt.nil?
#生成encrypted_password
self.encrypted_password = BCrypt::Engine.hash_secret(password, self.salt)
end
end
關於BCrypt::Engine的具體用法,請查閱文檔。
四,修改密碼
與註冊用戶一樣,只是根據新的password與原來的salt重新生成encrypted_password。
self.encrypted_password = BCrypt::Engine.hash_secret(new_password, self.salt)
五,登錄驗證
引用上文:在用戶登錄的時候,根據用戶輸入的email(或名字)在數據庫中查詢,取得對應的user,將用戶輸入的password與數據庫裏的user.salt進行同樣的哈希運算,將得到的結果與數據庫中存儲的user.encrypted_password進行比較,如果相等,證明賬號密碼正確,允許登錄。成功登錄後,將數據庫中的user.name與user.salt進行哈希運算,得出user_token存成cookie["user_token"],同時存儲cookie["user_id"]。下次登錄時嘗試讀取該cookie,取得user_id,根據user_id獲取數據庫中對應的user,user.name與user.salt進行哈希運算,將得到的結果與cookie["user_token"]比較,如果相同,則登錄成功。
情況一,首次登錄
登錄驗證:
def authenticated?(password)
#驗證密碼,返回boolen
BCrypt::Engine.hash_secret(password, self.salt) == self.encrypted_password
end
生成cookie:
def gen_token
#返回生成的user_token
Digest::SHA1.hexdigest(self.email + self.salt)
end
#保存user_id
cookies[:user] = { value: user.id, expires: 20.years.from_now.utc }
#保存user_token
cookies[:user_token] = { value: user.gen_token(), expires: 20.years.from_now.utc }
情況二,非首次,自動登錄
def valid_token?(token)
gen_token() == token
end
def gen_token
Digest::SHA1.hexdigest(self.email + self.salt)
end
def login?
if !cookies[:user].present? || !cookies[:user_token].present?
return false
else
begin
#從cookie中獲取user_id
@current_user = User.find(cookies[:user])
rescue
@current_user = nil
return false
end
#利用user_token進行登陸認證
if !@current_user.valid_token?(cookies[:user_token])
@current_user = nil
return false
end
end
return true
end
六,rails
用戶驗證應該在程序開始的時候進行。在ApplicationController裏面進行用戶驗證技能滿足我們的需求。
class ApplicationController < ActionController::Base
#設置爲before_action在程序運行前被調用
before_action :require_login
def require_login
#根據是否登錄跳轉到不同頁面
redirect_to :controller => 'login', :action => 'index' if !login?
end
#判斷以前是否登錄過
def login?
if !cookies[:user].present? || !cookies[:user_token].present?
return false
else
begin
@current_user = User.find(cookies[:user])
rescue
@current_user = nil
return false
end
if !@current_user.valid_token?(cookies[:user_token])
@current_user = nil
return false
end
end
return true
end
end
七,一些思考
在cookie中爲什麼不存用戶的郵箱而存user_token?
① 從數據安全的角度想,即使是cookie也不應該以明文的方式存儲用戶的信息(郵箱)。
②更新:其實上面的想法有點幼稚,因爲存郵箱根本不可行,在數據庫已有user表項的基礎上,郵箱與salt進行哈希運算得出的結果根本無法與其他的做比較,除非數據庫裏面又存了一個user_token用來作比較,這樣增加了數據庫的負擔,不是個好的選擇。