几乎每一个web应用都需要表格登录。在Ruby中,最流行的选择就是在Rails应用中实用Devise模块。
Devise提供了许多盒子之外的功能,但这也同样是令人沮丧的地方。它有很多的“魔法”,并且和Rails的很多功能高度耦合。但是如果你的需求和这个gem并不是很切合,举例说,匿名用户-你将需要翻阅整个文档并且阅读源码,来查询如何实现它。这一点也不可爱,并且看上去一点也不值。如果未来的一段时间内有验证需求的改变,这个改变对你来说看起来容易实现还是一场噩梦?
验证事实上是非常简单的!如果你的需求足够简单,你将可以在100行代码以内实现你的验证功能。在这片文章中,我将给你展示一个小而美的包含登录,登出和用户创建的小的web应用。
所有的源码都可以在这里找到:
simple_authentication.rb
依赖###
唯一的依赖就是用于创建密码的BCrypt gem。我们可以直接使用它,在Rails和Devise中都已经默认包含了。
在本例中,我们将使用Sinatra,由于BCrypt 解藕起来非常容易,因此代码可以轻松地翻译成Rails,这跟Devise很不一样。
Gemfile:
source 'https://rubygems.org'
gem 'sinatra', '~>1.4'
gem 'bcrypt', '~>3.1'
BCrypt实现哈希密码###
需要大约两行代码来实现这个需求:
def hash_password(password)
BCrypt::Password.create(password).to_s
end
def test_password(password, hash)
BCrypt::Password.new(hash) == password
end
hash_password函数生成了一个普通文本密码,由单向加密算法加密。要想基于哈希来破解密码,是一件很困难的、几乎不可能实现的事情。我们从不存储密码,只有哈希。所以,当有黑客进入数据库的事情发生,我们的密码依然是手保护的。
test_password函数用于检测当给定的密码是否和之前函数给出的hash一致。由于我们不在拥有原始密码,所以这次通过给定密码的hash和之前的hash相比较。
这是基本的密码安全,而这所有的工作都交由bcrypt gem来实现。
User Model###
为了让本例看起来足够简单,User模型只是一个Struct,而“数据库”就是一个数组。
User = Struct.new(:id, :username, :password_hash)
USERS = [
User.new(1,'Bob',hash_password('The building'))
User.new(2, 'Sally', hash_password('go round'))
]
每个用户都只有三个基本属性:id,username,hash。如果这是在Rails当中,你可以创建一个只有这三个属性的模型。
Signing In###
下列代码运行于用户提交登录表格:
post '/sign_in' do
user = USERS.find{ |u| u.username == params[:username]}
if user && test_password(params[:password],user.password_hash)
session.clear
session[:user_id] = user.id
redirect '/'
else
@error = 'Username or password was incorrect'
erb :sign_in
end
end
安全须知:一旦成功登录,我们在存储用户名之前情调session,这是一种阻止Session Fixation Attacks的安全策略
访问当前用户###
一旦用户登入,我们需要知道未来谁来在发送请求。下述代码可以返回当前用户:
def current_user
if session[:user_id]
USERS.find { |u| u.id == session[:user_id] }
else
nil
end
end
当我们在登入状态时,检查session是否包含user id,当user id存在时,根据用户id找到user。如果是在Rails当中,又可以用User.find(session[:user_id]).
在主页中,我们可以使用current_user:
<p>Hello, <%= current_user.username %></p>
Signing Out###
如下:
post '/sign_out' do
session.clear
redirect '/sign_in'
end
由于用户id存储在session中,我们只需要清除session来sign out。简单而又优美!
创建一个新用户###
代码如下:
post '/create_user' do
USERS << User.new(
USERS.size + 1,
params[:username],
hash_password(params[:password])
)
redirect '/'
end
在这个小小的实例中,我们创建了一个User对象,并将它添加到user数组中。
结束语###
拥有一个基本验证的工作实例一点也不难,不是吗?