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_password
và salt
.
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 Getter và Setter 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à password
và salt
. 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 name
và password,
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, create
và user_params.
Hai phương thức update
và create
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ố password
và password_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
, password
và password_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_password
và salt,
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ố password
và password_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: