Ruby Rails学习中:添加安全密码

接上篇

一. 添加安全密码

我们已经为 name 和 email 字段添加了验证规则, 现在要加入用户所需的最后一个常规属性: 安全密码。每个用户都要设置一个密码(还要二次确认), 数据库中则存储经过哈希(hash)加密后的密码(这里的hash是加密算法)。

验证身份的方法是, 获取用户提交的密码, 哈希加密, 再与数据库中存储的密码哈希值对比。如果二者一致, 用户提交的就是正确的密码, 用户的身份也就通过验证了。我们要对比的是密码哈希值, 而不是原始密码, 所以不用在数据库中存储用户的密码。因此, 就算被“脱库”了, 用户的密码仍然安全。

1.计算密码哈希值

我们使用的安全密码机制基本上用一个 Rails 方法即可实现,这个方法是 has_secure_password 。我们要在User 模型中调用这个方法, 如下所示:

在模型中调用这个方法后, 会自动添加如下功能:

• 在数据库中的 password_digest 列存储安全的密码哈希值;
• 获得一对虚拟属性, 18 password 和 password_confirmation ,而且创建户对象时会执行存在性验证和匹配验证;
• 获得 authenticate 方法,如果密码正确,返回对应的用户对象,否则返回 false 。

has_secure_password 发挥功效的唯一要求是, 对应的模型中有个名为 password_digest 的属性。(digest(摘要)是哈希加密算法中的术语。“密码哈希值”和“密码摘要”是一个意思。) 对 User 模型来说,我们要实现下图所示的数据模型。

为了实现上图中的数据模型, 首先要创建一个适当的迁移文件, 添加 password_digest 列。迁移的名字随意, 不过最好以 to_users 结尾, 因为这样 Rails 会自动生成一个向 users 表添加列的迁移。我们把这个迁移命名为 add_password_digest_to_users , 生成迁移的命令如下:

$ rails generate migration add_password_digest_to_users password_digest:string

注:在这个命令中,我们还加入了参数 password_digest:string , 指定想添加的列名和类型。加入 password_digest:string 后, 我们为 Rails 提供了足够的信息, 它会为我们生成一个完整的迁移, 如下图所示:

(1).向 users 表添加 password_digest 列的迁移

打开文件:db/migrate/[timestamp]_add_password_digest_to_users.rb

这个迁移使用 add_column 方法把 password_digest 列添加到 users 表中。执行下述命令在数据库中运行迁移:

$ rails db:migrate

has_secure_password 方法使用先进的 bcrypt 哈希算法计算密码摘要。使用 bcrypt 计算密码哈希值, 就算攻击者设法获得了数据库副本也无法登录网站。为了在演示应用中使用 bcrypt, 我们要把 bcrypt gem 添加到 Gem-file 文件中, 如下图所示:

(2).把 bcrypt gem 添加到 Gemfile 文件中

执行 bundle install 命令:

$ bundle install

2.用户有安全的密码

现在我们已经在 User 模型中添加了 password_digest 属性, 也安装了 bcrypt, 下面可以在 User 模型中添加 has_secure_password 方法了, 如下图所示:

(1).在 User 模型中添加 has_secure_password 方法 RED

打开文件:app/models/user.rb

我们在前面说过, has_secure_password 会在 password 和 password_confirmation 两个虚拟属性上执行验证, 但是现在前面创建 @user 变量时没有设定这两个属性:

所以,为了让测试组件通过, 我们要添加这两个属性, 如下图所示:

(2).添加密码和密码确认 GREEN

打开文件:test/models/user_test.rb

现在测试应该可以通过了:

3.密码的最短长度

一般来说, 最好为密码做些限制, 让别人更难猜测。在 Rails 中增强密码强度有很多方法, 简单起见, 我们只限制最短长度, 而且要求密码不能为空。最短长度为 6 是个不错的选择, 针对这个验证的测试如下图所示:

(1).测试密码的最短长度 RED

打开文件:test/models/user_test.rb

注意这段代码中使用的双重赋值:
@user.password = @user.password_confirmation = "a" * 5
这行代码同时为 password 和 password_confirmation 赋值,值是长度为 5 的字符串,使用字符串连乘创建。
参照 name 属性的 maximum 验证,你或许能猜到限制最短长度所需的代码:
validates :password, length: { minimum: 6 }
在上述代码的基础上,还要加上存在性验证,得出的 User 模型如下图所示。( has_secure_pass-word 方法本身会验证存在性,但是可惜,只会验证有没有密码,因此用户可以创建 “”(6 个空格)这样的无效密码。)

(2).实现安全密码的全部代码 GREEN

打开文件:app/models/user.rb

现在,测试应该可以通过了:

4.创建并验证用户的身份

至此, 基本的 User 模型已经完成了。接下来, 我们要在数据库中创建一个用户, 为以后开发的用户资料页面做准备。同时也看一下在 User 模型中添加 has_secure_password 方法后的效果, 还要用一下重要的 authen-ticate 方法

因为现在还不能在网页中注册, 我们要在 Rails 控制台中手动创建新用户。为了方便, 我们会使用前面所说的 create 方法。注意, 不要在沙盒模式中启用控制台, 否则结果不会存入数据库。我们要使用 rails console 启动普通的控制台, 然后使用有效的名字和电子邮件地址, 以及密码和密码确认, 创建一个用户:

注:这里, 我出了一个莫名奇妙的BUG(上图是我改完BUG之后才能通过的结果), 先暂停补充一下:

(1).修改BUG

BUG内容为:

You don't have bcrypt installed in your application. 
Please add it to your Gemfile and run bundle install

我Gemfile文件已经添加并按装过bcrypt, 并且装了好几个版本都出现了这个问题

解决步骤:先删掉现有的所有的bcrypt包

gem uninstall bcrypt

查看gem列表是否还存在bcrypt

gem list bcrypt

然后:关掉IDE和正在运行的Ruby Rails项目

再关掉正在使用的终端

然后重新打开终端,定位到自己的项目,添加好Gemfile文件中的包

bundle install

然后就好了,莫名奇妙。。。

(2).我们继续

好了改完BUG, 为了确认结果, 我们使用 SQLite 数据库浏览器查看开发数据库( db/development.sqlite3 )中的 users 表, 如下图所示:

回到控制台,查看 password_digest 属性的值, 由此可以看出 has_secure_password 方法的作用:

注:这是创建用户对象时指定的密码( "foobar" )的哈希值。这个值由 bcrypt 计算得出, 很难反推出原始密码。

前面说过, has_secure_password 方法会自动在对应的模型对象中添加 authenticate 方法。这个方法会计算给定密码的哈希值, 然后与数据库中 password_digest 列的值比较, 以此判断用户提供的密码是否正确。

我们可以在刚创建的用户上试几个错误密码:

我们提供的密码都是错误的, 所以 user.authenticate 返回 false 。如果提供正确的密码, authenticate 方法会返回数据库中对应的用户

会使用 authenticate 方法把注册的用户登入网站。其实, authenticate 方法返回的用户对象并不重要, 关键是这个值是“真值”。前面说过,  !! 会把对象转换成相应的布尔值。

我们可以使用这种方式确认:

。。。

下班了,先到这把,做个总结:

二. 总结

• 使用迁移可以修改应用的数据模型;
• Active Record 提供了很多创建和处理数据模型的方法;
• 使用 Active Record 验证可以在模型的数据上添加约束条件;
• 常见的验证有存在性、长度和格式;
• 正则表达式晦涩难懂,但功能强大;
• 数据库索引可以提升查询效率,而且能在数据库层实现唯一性约束;
• 可以使用内置的 has_secure_password 方法在模型中添加一个安全的密码。
posted @ 2019-10-23 19:08  骑驴老神仙  阅读(566)  评论(0编辑  收藏  举报