Rails – Xác thực user – Phần 1


Được đăng vào ngày 06/12/2016 | 0 bình luận
Rails – Xác thực user – Phần 1
5 (100%) 5 votes

Trong phần này chúng ta sẽ tạo cấu trúc lưu trữ thông tin user (người dùng).

Ở đây chúng ta chỉ lưu trữ đơn giản, bao gồm username và password, nhưng password sẽ không được lưu bình thường mà chúng ta sẽ mã hóa bằng thuật toán SHA2, do đó password trong CSDL sẽ gồm 2 thành phần là hashed (mật khẩu đã được mã hóa) và salt (giá trị salt).

Nếu bạn chưa biết về kiểu mã hóa này thì có thể hiểu đơn giản là khi người dùng gửi mật khẩu lên để đăng ký, chúng ta sẽ tạo ra một ký tự chuỗi ngẫu nhiên có độ dài cố định gọi là salt, sau đó dùng một thuật toán mã hóa (ở đây là SHA2) để mã hóa chuỗi mật khẩu với chuỗi salt để cho ra chuỗi hashed hay còn gọi là mật khẩu đã được mã hóa. Về thuật toán SHA2 thì ở đây chúng ta dùng thư viện, nếu bạn muốn biết chi tiết cách hoạt động thì có thể tìm hiểu trên mạng.

Định nghĩa model user

Đầu tiên chúng ta tạo model rồi cập nhật lên CSDL:

C:\Projects\Rails\depot>rails generate scaffold user name:string hashed_password:string salt:string
...
C:\Projects\Rails\depot>rake db:migrate

Bảng user của chúng ta sẽ gồm 3 trường là name, hashed_passwordsalt.

File user.rb được tạo ra, chúng ta sửa lại file này như sau:

require 'digest/sha2'

class User < ActiveRecord::Base
    validates :name, :presence => true, :uniqueness => true
    validates :password, :confirmation => true
    attr_accessor :password_confirmation
    attr_reader :password 
 
    validate :password_must_be_present
 
    def User.encrypt_password(password, salt) 
        Digest::SHA2.hexdigest(password + salt)
    end
 
    def password=(password) 
        @password = password
 
        if password.present?
            generate_salt
            self.hashed_password = self.class.encrypt_password(password, salt)
        end
    end
 
    def User.authenticate(name, password) 
        if user = find_by_name(name)
            if user.hashed_password == encrypt_password(password, user.salt)
               user
            end
        end
    end

private
    def password_must_be_present 
        if hashed_password.present? == false
            errors.add(:password, "Missing password")
        end
    end
 
    def generate_salt
        self.salt = self.object_id.to_s + rand.to_s
    end
end

Lớp này sẽ hơi phức tạp một chút.

require 'digest/sha2'

Ngay dòng đầu tiên chúng ta gọi thư viện SHA2, đây là thư viện có sẵn trong Ruby.

validates :name, :presence => true, :uniqueness => true
validates :password, :confirmation => true

Chúng ta kiểm tra xem biến name có tồn tại hay không, và name không được trùng nhau. Ngoài ra biến password phải được nhập 2 lần và cả 2 phải giống nhau, tức là khi chúng ta đăng ký thì người dùng gõ mật khẩu, sau đó phải gõ lần nữa để xác nhận.

attr_accessor :password_confirmation
attr_reader :password 

attr_accessor là phương thức tạo nhanh GetterSetter của Ruby, ở đây là biến password_confimation, tương tự attr_reader tạo Getter cho biến password.

validate :password_must_be_present

Ngoài ra chúng ta còn kiểm tra xem biến hashed_password có tồn tại hay không, tức là kiểm tra xem mật khẩu đã được mã hóa hay chưa. Chúng ta kiểm tra bằng cách định nghĩa phương thức password_must_be_present:

private
    def password_must_be_present 
        if hashed_password.present? == false
            errors.add(:password, "Missing password")
        end
    end

Nếu chưa có thì chúng ta cho báo lỗi.

Tiếp theo chúng ta định nghĩa hàm thực hiện mã hóa password:

    def User.encrypt_password(password, salt) 
        Digest::SHA2.hexdigest(password + salt)
    end
.
.
.
private
.
.
.
    def generate_salt
        self.salt = self.object_id.to_s + rand.to_s
    end

Hàm encrypt_password sẽ gọi hàm Digest::SHA2.hexdigest và nhận vào tham số là passwordsalt. Kết quả sẽ cho ra một chuỗi 64 kí tự (256 bit). Salt sẽ được tạo từ hàm generate_salt, chúng ta cho tạo chuỗi salt từ id của chính đối tượng User đó rồi nối với một chuỗi số ngẫu nhiên tạo từ hàm rand.

def password=(password) 
    @password = password
 
    if password.present?
        generate_salt
        self.hashed_password = self.class.encrypt_password(password, salt)
    end
end

Chúng ta định nghĩa lại toán tử = trên biến password, để khi người dùng gửi password lên, thì password sẽ không được gán thẳng vô biến mà sẽ được mã hóa lại rồi mới lưu vào CSDL.

def User.authenticate(name, password) 
    if user = find_by_name(name)
        if user.hashed_password == encrypt_password(password, user.salt)
           user
        end
    end
end

Chúng ta định nghĩa phương thức authenticate để xác thực người dùng, khi người dùng đăng nhập với namepassword, chúng ta mã hóa password đó với chuỗi salt của người dùng lưu trong CSDL và kiểm tra xem chuỗi vừa được mã hóa có giống như chuỗi đã được mã hóa trong CSDL không, nếu giống thì trả về đối tượng User, tức là đăng nhập thành công.

Tùy chỉnh controller

Chúng ta sửa lại một số phương thức trong lớp UsersController như sau:

class UsersController < ApplicationController
    .
    .
    .
    # POST /users
    # POST /users.json
    def create 
        @user = User.new(user_params)
 
        respond_to do |format|
            if @user.save 
                format.html { redirect_to users_url, notice: 'User was successfully created.' }
                format.json { render :show, status: :created, location: @user }
            else 
                format.html { render :new }
                format.json { render json: @user.errors, status: :unprocessable_entity }
            end
        end
    end
    .
    .
    .
    # PATCH/PUT /users/1
    # PATCH/PUT /users/1.json
    def update
        respond_to do |format|
            if @user.update(user_params)
                format.html { redirect_to users_url, notice: 'User was successfully updated.' }
                format.json { render :show, status: :ok, location: @user }
            else
                format.html { render :edit }
                format.json { render json: @user.errors, status: :unprocessable_entity }
            end
        end
    end
    .
    .
    .
    # Never trust parameters from the scary internet, only allow the white list through.
    def user_params
        params.require(:user).permit(:name, :hashed_password, :salt, :password, :password_confirmation)
    end
end

Chúng ta sửa lại 3 phương thức là update, createuser_params.

Hai phương thức updatecreate thì chúng ta chỉ sửa đơn giản là cho redirect về trang /users thôi, thay vì trang /users/<id>.

Còn phương thức user_params thì chúng ta sửa lại phương thức params.requrie().permit cho nhận thêm tham số passwordpassword_confirmation. Khi người dùng nhập form trên trình duyệt, sau đó bấm nút gửi thì trình duyệt sẽ gửi lệnh HTTP kèm theo các tham số lên server, các tham số đó sẽ nằm trong biến params, tùy nhiên mặc định Rails sẽ không chấp nhận các tham số được gửi lên vì lý do bảo mật, do đó nếu muốn một tham số nào được nhận thì chúng ta phải khai báo trong phương thức permit(). Trong số các tham số gửi lên khi đăng ký tài khoản có một tham số tên là user, tham số này lại gồm 3 tham số khác là name, passwordpassword_confirmation.

Mặc định Rails chỉ nhận các tham số có tên trùng với tên trong model thôi, tức là chỉ có name, hashed_passwordsalt, nhưng khi đăng ký tài khoản thì người dùng đâu có nhập hashed và salt mà chỉ nhập password bình thường. Do đó chúng ta phải đưa thêm tham số passwordpassword_confirmation thì Rails mới cho phép “đi qua” controller.

Tùy chỉnh view

Đầu tiên chúng ta sửa file index.html.erg trong thư mục app/views/users như sau:

<p id="notice"><%= notice %></p>

<h1>Listing Users</h1>

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th></th>
            <th></th>
            <th></th>
        </tr>
    </thead>

    <tbody>
        <% @users.each do |user| %>
        <tr>
            <td><%= user.name %></td> 
            <td><%= link_to 'Show', user %></td>
            <td><%= link_to 'Edit', edit_user_path(user) %></td>
            <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
        <% end %>
    </tbody>
</table>

<br>

<%= link_to 'New User', new_user_path %>

Mặc định file này hiển thị cả giá trị hashed và salt nữa, ở đây chúng ta bỏ các dòng đó đi.

Tiếp theo chúng ta sửa file _form.html.erb trong thư mục app/views/users như sau:

<div class="depot_form">

    <%= form_for @user do |f| %>
        <% if @user.errors.any? %>
            <div id="error_explanation">
                <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved: </h2>
                <ul>
                    <% @user.errors.full_messages.each do |msg| %>
                        <li><%= msg %></li>
                    <% end %>
                </ul>
            </div>
        <% end %>
 
        <fieldset>
        <legend>Enter User Details</legend>
 
        <div>
            <%= f.label :name %>:
            <%= f.text_field :name, :size => 40 %>
        </div>
 
        <div>
            <%= f.label :password, 'Password' %>:
            <%= f.password_field :password, :size => 40 %>
        </div>
  
        <div>
            <%= f.label :password_confirmation, 'Confirm' %>:
            <%= f.password_field :password_confirmation, :size => 40 %>
        </div>
 
        <div>
            <%= f.submit %>
        </div> 
        </fieldset>
    <% end %>
</div>

Chúng ta sửa lại để hiển thị theo các lớp CSS chúng ta đã định nghĩa.

Bây giờ chúng ta có thể trỏ đăng ký tài khoản được rồi:

capture







Trả lời


Lưu ý: bọc code trong cặp thẻ [code language="x"][/code] để highlight code.


Ví dụ:


[code language="cpp"]


    std::cout << "Hello world";


[/code]



Các ngôn ngữ được hỗ trợ gồm: actionscript3, bash, clojure, coldfusion, cpp, csharp, css, delphi, diff, erlang, fsharp, go, groovy, html, java, javafx, javascript, latex, matlab, objc, perl, php, powershell, python, r, ruby, scala, sql, text, vb, xml.

Thư điện tử của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *