Ruby on Rails 網站程式設計基礎班

第八課:使用者認證

使用者認證

網頁需要辨識使用者身份,查看該使用者是否有權限能做某些事 (像是貼文、留言)
或使用某些特定的功能 (像是只有管理者能夠刪除貼文)

Sessions

1. HTTP 溝通協定是無狀態,代表它不會記住使用者狀態 (登入狀態,購物車內容 etc.)

2. Server 與 Client 之間不是一直保持連線

3. 為了讓 browser 能跨 request 記住資訊,使用 session 來儲存資訊

session 資訊是由 server 端產出

4. 為了使用者體驗,session 資訊通常會放在 cookie 裡面

5. cookie 是 browser 的暫存空間,只有 4k 的大小

 

 

 


 

Sessions

每一次使用者登入都會建立一個 session id

這個 session id 會被加密,然後儲存到 cookie 裡面

Client 每次發 request 時,server 就會讀取 session id

使用者登出後,該 session id 才會被銷毀

 

 

 


 

增加 user model

因為我是對 User 這個 Model 做驗證,因此我們需要一個 users table 來記錄使用者的資訊

 

 

 


 

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end
end
$rails g model user --no-test-framework

我們可以用以下指令直接產生 migration 檔與 model 檔:

接下來再編輯 migration 檔:

Sessions

Rails 內建的使用者認證功能:has_secure_password

使用此功能需要安裝 bcrypt gem

 

 

 

 


 

# 把以下加入 Gemfile

gem 'bcrypt', '~> 3.1.7'
# 然後在 User Model 裡加上

class User < ActiveRecord::Base
  has_secure_password validation: false
end

# validation: false 是避免使用者需要輸入密碼兩次

session

記得使用 has_secure_password 時,users table 需要加入

password_digest 欄位:

 

 

 

 

 


 

class AddPasswordDigestToUsersTable < ActiveRecord::Migration
  def change
    add_column :users, :password_digest, :string
  end
end

session

在 Rails 實作 session 十分簡單:

 

 

 

 


 

class SessionsController < ApplicationController
  def new
    #登入頁面使用
  end

  def create
    #尋找使用者
    user = User.find_by(name: params[:name])   
    #驗證使用者,若成功,就建立一個 session,把 user_id 放入 session hash
    if user && user.authenticate(params[:password])
      session[:user_id] = user.id
      redirect_to root_path
    else
      redirect_to login_path
    end
  end

  def destroy
    #登出畫面使用,刪除 session hash 裡面的 user_id
    session[:user_id] = nil
    redirect_to root_path
  end
end

在這裡可以清楚看到,session 是一個 hash, user_id 在被加密過後,會傳至 client 端的 cookie store

session

加上登入與登出的 url:

 

 

 

 


 

# config/routes.rb

get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
get '/logout', to: 'sessions#destroy'

session

登入畫面的 template:

 

 

 

 


 

<%= render '/shared/title', title: '使用者登入'%>

<div class="well">
  <%= form_tag '/login' do |f| %>
  <div class="form-group">
    <%= label_tag :name %>
    <br>
    <%= text_field_tag :name, params[:name] %>
  </div>
  <div class="form-group">
    <%= label_tag :password %>
    <br>
    <%= password_field_tag :password, params[:password] %>
  </div>
  <br>
  <%= submit_tag "登入", class: "btn btn-success" %>
  <% end %>
</div>

form_for vs form_tag

1. 兩者都是 Rails 用來建立表單的 helper

2. form_for 是會和 model 綁定的表單,像是對 user 或是 post 等 model 物件的新增和修改

3. form_tag 是沒有對應 model 的表單,適用像是登入、搜尋等功能

helper method

今天我若需要一個跨 controller 和 view 的方法:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception
    
  
  helper_method :current_user, :logged_in?
  
  def current_user
     @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end

  def logged_in?
    !!current_user
  end
end

helper method

有了 logged_in? 這個 helper method 後,就可以在前端做判斷了:

# app/views/layouts/_navigation.html.erb

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <%= link_to "歡樂碼農訂便當系統", orders_path, class: 'navbar-brand' %>
    </div>
    <div class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <% if logged_in? %>
          <li><%= link_to '新增訂單', new_order_path %></li>
          <li><%= link_to '登出', logout_path %></li>
        <% else %>
          <li><%= link_to '登入', login_path %></li>
        <% end %>
      </ul>
    </div>
  </div>
</nav>

RailsCasts

1. Rails 影音教學網站

2. 以實作功能為主的教學

3. 低廉的收費 ($10 USD)

用 hash 傳參數

# 若今天一個方法我需要傳進許多參數...
class Person
  # 宣告方法的介面時,要寫的 code 就會變得又臭又長...
  def initialize(name, age, hair_color, favorite_book, occupation)
    @name = name
    @age = age
    @hair_color = hair_color
    @favorite_book = favorite_book
    @occupation = occupation
  end
end

# 更麻煩的是,在使用上還必須依照順序傳入參數...
bob = Person.new("bob", 17, "black", "1984", "Salesman")

用 hash 傳參數

class Person
  attr_accessor :name, :age, :occupation
  # 可以把方法改成用 hash 傳遞參數
  def initialize(options ={})
    @name = options[:name] # 到hash內取值
    @age = options[:age]
    @hair_color = options[:hair_color]
    @favorite_book = options[:favorite_book]
    @occupation = options[:occupation]
  end
end

hash = { 
  hair_color: "black", 
  name: "bob", 
  age: "1984", 
  occupation: "Salesman", 
  favorite_book: "1984" 
}

# 使用時就丟 hash...
bob = Person.new(hash)
# 或這樣:
bob = Person.new(name: "bob", age: "1984", occupation: "Salesman", ...)

實作使用者註冊功能

# app/controllers/users_controllers.rb

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to root_path
    else
      render :new
    end
  end

  private 

  def user_params
    params.require(:user).permit(:name, :password)
  end
end

實作使用者註冊功能

# app/views/users/new.html.erb

<h4>Register</h4>

<div class="well col-md-6">
  <%= form_for @user do |f| %>
  <div class="form-group">
    <%= f.label :name %>
    <br>
    <%= f.text_field :name %>
  </div>
  <div class="form-group">
    <%= f.label :password %>
    <br>
    <%= f.password_field :password%>
  </div>
  <br>
  <%= f.submit(@user.new_record? ? 'Register' : 'Update Profile', class: 'btn btn-success') %>
  <% end %>
</div>

實作使用者註冊功能

# config/routes.rb

resources :users, only: [:new, :create]

get '/register', to: 'users#new'

實作使用者註冊功能

# config/routes.rb

resources :users, only: [:new, :create]

get '/register', to: 'users#new'

重構 Navigation Bar

<nav class="navbar navbar-default">
  <div class="container-fluid">
    <div class="navbar-header">
      <%= link_to '歡樂碼農訂便當系統', root_path, class: 'navbar-brand' %>
    </div>
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <% if logged_in? %>
          <li><%= link_to '新增訂單', new_order_path %></li>
        <% else %>
          <li><%= link_to '註冊', register_path %></li>
        <% end %>
      </ul>
      <ul class="nav navbar-nav navbar-right">
        <% if logged_in? %>
          <li class="dropdown">
            <%= link_to '#', class: 'dropdown-toggle', 'data-toggle' => 'dropdown' do %>
              <%= current_user.name %> <span class='caret'></span>
            <% end %>
            <ul class="dropdown-menu">
              <li role="separator" class="divider"></li>
              <li><%= link_to '登出', logout_path %></li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to '登入', login_path %></li>
        <% end %>
      </ul>
    </div>
  </div>
</nav>

功課

開始構思 + 實作期末 project

第十節課報告你要做的東西

兩個星期後展示你的作品

Made with Slides.com