<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 6.3 添加安全密碼 我們已經為 `name` 和 `email` 字段添加了驗證規則,現在要加入用戶所需的最后一個常規屬性:安全密碼。每個用戶都要設置一個密碼(還要二次確認),數據庫中則存儲經過哈希加密后的密碼。(你可能會困惑。這里所說的“哈希”不是 [4.3.3 節](chapter4.html#hashes-and-symbols)介紹的 Ruby 數據結構,而是經過不可逆[哈希算法](http://en.wikipedia.org/wiki/Hash_function)計算得到的結果。)我們還要加入基于密碼的認證驗證機制,[第 8 章](chapter8.html#log-in-log-out)會利用這個機制實現用戶登錄功能。 認證用戶的方法是,獲取用戶提交的密碼,哈希加密,再和數據庫中存儲的密碼哈希值對比,如果二者一致,用戶提交的就是正確的密碼,用戶的身份也就通過認證了。我們要對比的是密碼哈希值,而不是原始密碼,所以不用在數據庫中存儲用戶的密碼。因此,就算被脫庫了,用戶的密碼仍然安全。 ## 6.3.1 計算密碼哈希值 我們使用的安全密碼機制基本上由一個 Rails 方法即可實現,這個方法是 `has_secure_password`。我們要在用戶模型中調用這個方法,如下所示: ``` class User < ActiveRecord::Base . . . has_secure_password end ``` 在模型中調用這個方法后,會自動添加如下功能: * 在數據庫中的 `password_digest` 列存儲安全的密碼哈希值; * 獲得一對“虛擬屬性”,[[17](#fn-17)]`password` 和 `password_confirmation`,而且創建用戶對象時會執行存在性驗證和匹配驗證; * 獲得 `authenticate` 方法,如果密碼正確,返回對應的用戶對象,否則返回 `false`。 `has_secure_password` 發揮功效的唯一要求是,對應的模型中有個名為 `password_digest` 的屬性。(“digest”(摘要)是[哈希加密算法](http://en.wikipedia.org/wiki/Cryptographic_hash_function)中的術語。“密碼哈希值”和“密碼摘要”是一個意思。)[[18](#fn-18)]對用戶模型來說,我們要實現如[圖 6.7](#fig-user-model-password-digest) 所示的數據模型。 ![user model password digest 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd0d53d67.png)圖 6.7:用戶數據模型,多了一個 `password_digest` 屬性 為了實現[圖 6.7](#fig-user-model-password-digest) 中的數據模型,首先要創建一個適當的遷移文件,添加 `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`,指定想添加的列名和類型。(和[代碼清單 6.1](#listing-generate-user-model) 中的命令對比一下,那個命令生成創建 `users` 表的遷移,指定了 `name:string` 和 `email:string` 兩個參數。)加入 `password_digest:string` 后,我們為 Rails 提供了足夠的信息,它會為我們生成一個完整的遷移,如[代碼清單 6.32](#listing-password-migration) 所示。 ##### 代碼清單 6.32:在 `users` 表中添加 `password_digest` 列的遷移 db/migrate/[timestamp]_add_password_digest_to_users.rb ``` class AddPasswordDigestToUsers < ActiveRecord::Migration def change add_column :users, :password_digest, :string end end ``` 這個遷移使用 `add_column` 方法把 `password_digest` 列添加到 `users` 表中。執行下述命令在數據庫中運行遷移: ``` $ bundle exec rake db:migrate ``` `has_secure_password` 方法使用先進的 [bcrypt](http://en.wikipedia.org/wiki/Bcrypt) 哈希算法計算密碼摘要。使用 bcrypt 計算密碼哈希值,就算攻擊者設法獲得了數據庫副本也無法登錄網站。為了在演示應用中使用 bcrypt,我們要把 `bcrypt` gem 添加到 `Gemfile` 中,如[代碼清單 6.33](#listing-bcrypt-ruby) 所示。 ##### 代碼清單 6.33:把 `bcrypt` gem 添加到 `Gemfile` 中 ``` source 'https://rubygems.org' gem 'rails', '4.2.2' gem 'bcrypt', '3.1.7' . . . ``` 然后像往常一樣,執行 `bundle install` 命令: ``` $ bundle install ``` ## 6.3.2 用戶有安全的密碼 現在我們已經在用戶模型中添加了 `password_digest` 屬性,也安裝了 bcrypt,下面可以在用戶模型中添加 `has_secure_password` 方法了,如[代碼清單 6.34](#listing-has-secure-password) 所示。 ##### 代碼清單 6.34:在用戶模型中添加 `has_secure_password` 方法 RED app/models/user.rb ``` class User < ActiveRecord::Base before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password end ``` 如[代碼清單 6.34](#listing-has-secure-password) 中的“**RED**”所示,測試現在失敗,我們可以在命令行中執行下述命令確認: ##### 代碼清單 6.35:**RED** ``` $ bundle exec rake test ``` 我們在 [6.3.1 節](#a-hashed-password)說過,`has_secure_password` 會在 `password` 和 `password_confirmation` 兩個虛擬屬性上執行驗證,但是現在[代碼清單 6.25](#listing-validates-uniqueness-of-email-case-insensitive-test) 中的 `@user` 變量沒有這兩個屬性: ``` def setup @user = User.new(name: "Example User", email: "user@example.com") end ``` 所以,為了讓測試組件通過,我們要添加這兩個屬性,如[代碼清單 6.36](#listing-test-with-password-confirmation) 所示。 ##### 代碼清單 6.36:添加密碼和密碼確認 GREEN test/models/user_test.rb ``` require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . end ``` 現在測試應該可以通過了: ##### 代碼清單 6.37:**GREEN** ``` $ bundle exec rake test ``` [6.3.4 節](#creating-and-authenticating-a-user)會看到在用戶模型中添加 `has_secure_password` 的作用。在此之前,為了密碼的安全,先添加一個小要求。 ## 6.3.3 密碼的最短長度 一般來說,最好為密碼做些限制,讓別人更難猜測。在 Rails 中增強密碼強度有很多方法,簡單起見,我們只限制最短長度,而且要求密碼不能為空。最短長度為 6 是個不錯的選擇,針對這個驗證的測試如[代碼清單 6.38](#listing-minimum-password-length-test) 所示。 ##### 代碼清單 6.38:測試密碼的最短長度 RED test/models/user_test.rb ``` require 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "password should be present (nonblank)" do @user.password = @user.password_confirmation = " " * 6 assert_not @user.valid? end test "password should have a minimum length" do @user.password = @user.password_confirmation = "a" * 5 assert_not @user.valid? end end ``` 注意這段代碼中使用的雙重賦值: ``` @user.password = @user.password_confirmation = "a" * 5 ``` 這行代碼同時為 `password` 和 `password_confirmation` 賦值,值是長度為 5 的字符串,使用字符串連乘創建。 參照 `name` 屬性的 `maximum` 驗證([代碼清單 6.16](#listing-length-validation)),你或許能猜到限制最短長度所需的代碼: ``` validates :password, length: { minimum: 6 } ``` 在上述代碼的基礎上,還要加上存在性驗證,得出的用戶模型如[代碼清單 6.39](#listing-password-implementation) 所示。(`has_secure_password` 方法本身會驗證存在性,但是可惜,只會驗證有沒有密碼,因此用戶可以創建 “ ”(6 個空格)這樣的無效密碼。) ##### 代碼清單 6.39:實現安全密碼的全部代碼 GREEN app/models/user.rb ``` class User < ActiveRecord::Base before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } end ``` 現在,測試應該可以通過了: ##### 代碼清單 6.40:**GREEN** ``` $ bundle exec rake test:models ``` ## 6.3.4 創建并認證用戶 至此,基本的用戶模型已經完成了。接下來,我們要在數據庫中創建一個用戶,為 [7.1 節](chapter7.html#showing-users)開發的用戶資料頁面做準備。同時也看一下在用戶模型中添加 `has_secure_password` 的效果,還要用一下重要的 `authenticate` 方法。 因為現在還不能在網頁中注冊([第 7 章](chapter7.html#sign-up)實現),我們要在控制臺中手動創建新用戶。為了方便,我們會使用 [6.1.3 節](#creating-user-objects)介紹的 `create` 方法。注意,不要在沙盒模式中啟用控制臺,否則結果不會存入數據庫。所以我們要使用 `rails console` 啟動普通的控制臺,然后使用有效的名字和電子郵件地址,以及密碼和密碼確認,創建一個用戶: ``` $ rails console >> User.create(name: "Michael Hartl", email: "mhartl@example.com", ?> password: "foobar", password_confirmation: "foobar") => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2014-09-11 14:26:42", updated_at: "2014-09-11 14:26:42", password_digest: "$2a$10$sLcMI2f8VglgirzjSJOln.Fv9NdLMbqmR4rdTWIXY1G..."> ``` 為了確認結果,我們使用 SQLite 數據庫瀏覽器看一下開發數據庫(`db/development.sqlite3`)中的 `users` 表,如[圖 6.8](#fig-sqlite-user-row) 所示。[[19](#fn-19)]留意[圖 6.7](#fig-user-model-password-digest) 中數據模型的各個屬性。 ![sqlite user row with password 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd0d62956.png)圖 6.8:SQLite 數據庫(`db/development.sqlite3`)中的一個用戶記錄 回到控制臺,查看 `password_digest` 屬性的值,由此可以看出[代碼清單 6.39](#listing-password-implementation)中 `has_secure_password` 的作用: ``` >> user = User.find_by(email: "mhartl@example.com") >> user.password_digest => "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQWITUYlG3XVy" ``` 這是創建用戶對象時指定的密碼(`"foobar"`)的哈希值。這個值由 bcrypt 計算得出,很難反推出原始密碼。[[20](#fn-20)] [6.3.1 節](#a-hashed-password)說過,`has_secure_password` 會自動在對應的模型對象中添加 `authenticate` 方法。這個方法會計算給定密碼的哈希值,然后和數據庫中 `password_digest` 列中的值比較,以此判斷用戶提供的密碼是否正確。我們可以在剛創建的用戶上試幾個錯誤密碼: ``` >> user.authenticate("not_the_right_password") false >> user.authenticate("foobaz") false ``` 我們提供的密碼都是錯誤的,所以 `user.authenticate` 返回 `false`。如果提供正確的密碼,`authenticate` 方法會返回數據庫中對應的用戶: ``` >> user.authenticate("foobar") => #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com", created_at: "2014-07-25 02:58:28", updated_at: "2014-07-25 02:58:28", password_digest: "$2a$10$YmQTuuDNOszvu5yi7auOC.F4G//FGhyQSWCpghqRWQW..."> ``` [第 8 章](chapter8.html#log-in-log-out)會使用 `authenticate` 方法把注冊的用戶登入網站。其實,`authenticate` 方法返回的用戶對象并不重要,關鍵是這個值是“真值”。因為用戶對象不是 `nil`,也不是 `false`,所以能很好地完成任務:[[21](#fn-21)] ``` >> !!user.authenticate("foobar") => true ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看