一,相关资料
惯例,先上文档,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用来作比较,这样增加了数据库的负担,不是个好的选择。