Link Search Menu Expand Document

使用者認證

Quality, Speed or Cheap. Pick two. - Unknown

使用者認證Authentication用以識別使用者身分,而授權Authorization則用來處理使用者有沒有權限可以作哪些事情。

Authentication: 使用 Devise

devise是一套使用者認證(Authentication)套件,是Rails社群中最廣為使用的一套。

  • 編輯 Gemfile 加上

      gem 'devise'
    
  • 輸入bundle install安裝此套件
  • 輸入rails g devise:install產生devise設定檔
  • 編輯 config/environments/development.rb 和 production.rb 加入寄信時預設的網站網址:

      config.action_mailer.default_url_options = { :host => 'localhost:3000' }
    
  • 確認 app/views/layouts/application.html.erb layout 中可以顯示 flash 訊息,例如

      <p class="notice"><%= notice %></p>
      <p class="alert"><%= alert %></p>
    
  • 確認 routes.rb 中有設定網站首頁位置,例如

      root :to => "welcome#index"
    
  • 輸入rails g devise user產生 User model 及 Migration
  • 輸入rails generate devise:views產生HTML樣板,這會包括有註冊、登入、忘記密碼、Email等等頁面,放在app/views/devise目錄下。
  • 輸入bin/rake db:migrate就會建立 users 資料表了

用法

  • 在需要登入的 controller 加上before_action :authenticate_user!
  • 可以在 Layout 中加上登入登出選單

      <% if current_user %>
        <%= link_to('登出', destroy_user_session_path, :method => :delete) %> |
        <%= link_to('修改密碼', edit_registration_path(:user)) %>
      <% else %>
        <%= link_to('註冊', new_registration_path(:user)) %> |
        <%= link_to('登入', new_session_path(:user)) %>
      <% end %>
    

加上自訂欄位到 Devise 的註冊和編輯頁面

Devise預設沒有產生出first_namelast_name等等欄位,例如我們來加一個nickname欄位到User Model

  • rails g migration add_nickname_to_users,加上

      add_column :users, :nickname, :string
    
  • rake db:migrate 新增這個欄位
  • 編輯application_controller.rb補上configure_permitted_parameters方法:

      class ApplicationController < ActionController::Base
        before_action :configure_permitted_parameters, if: :devise_controller?
    
        # ...
    
        protected
    
        def configure_permitted_parameters
          devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
          devise_parameter_sanitizer.permit(:account_update, keys: [:nickname])
        end
      end
    
  • 編輯views/devise/registrations/edit.html.erbviews/devise/registrations/new.html.erb,加上username欄位

      <div><%= f.label :nickname %><br />
      <%= f.text_field :nickname %></div>
    

Authentication: 使用 Omniauth

除了使用上述的Devise自行處理使用者帳號密碼之外,現在也非常流行直接使用外部的使用者認證系統,例如GoogleFacebookYahooGitHub等等,一來絕大部分的使用者都已經有了這些大網站的帳號,不需要再註冊一次。二來你也不需要擔心儲存密碼的安全性問題。這些第三方服務都使用一種叫做 OAuth 的開放標準,允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,影片,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。

這裡我們使用的套件是Omniauth,他可以搭配各種不同的Provider廠商:

Devise 也可以和 Omniauth 整合在一起,請參考這份 Devise 的文件: https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview

範例: 整合 Devise 和 Facebook 帳號登入

請上 https://developers.facebook.com/ 申請一個 App,拿到 App ID 和 App Secret 等會要用:

  • 開發用的 Site URL 請填 http://localhost:3000
  • 將來正式環境(production) 會另申請一個 App
  • 如果同一個產品有 Web、iOS 和 Android 版本,要共用同一個 Facebook App,這樣拿到的 Facebook user id 才對的起來。
  • 正式公開上線需要送審,不然只有開發者自己可以登入使用

接著參考 https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview 的步驟說明如下:

  1. 編輯 Gemfile加上 gem 'omniauth-facebook',然後執行 bundle
  2. 新增 migration rails g migration AddOmniauthToUsers,內容如下:(結束記得執行 rails db:migrate)

    class AddOmniauthToUsers < ActiveRecord::Migration
      def change
        add_column :users, :fb_uid, :string
        add_column :users, :fb_token, :string
    
        add_index :users, :fb_uid
      end
    end
    
  3. 編輯 config/initializers/devise.rb,加上一行 (改完要重開 server): 將 YOUR_FB_APP_ID 和 FB_APP_SECRET 改為 facebook developer 上拿到的內容

    config.omniauth :facebook, "YOUR_FB_APP_ID", "FB_APP_SECRET", :scope => 'public_profile,email', :info_fields => 'email,name', callback_url: "http://localhost:3000/users/auth/facebook/callback"
    
  4. 編輯 app/models/user.rb,加上:omniauthable, :omniauth_providers => [:facebook]如下:

    devise :database_authenticatable, :registerable,
           :recoverable, :rememberable, :trackable, :validatable,
           :omniauthable, :omniauth_providers => [:facebook]
    
  5. 編輯 config/routes.rb

    devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
    
  6. 編輯 app/views/layout/application.html.erb,加上 Facebook 登入的超連結:

    <% if current_user %>
      <%= link_to('登出', destroy_user_session_path, :method => :delete) %>
      ....
    <% else %>
        ....
        <%= link_to "登入 Facebook", user_facebook_omniauth_authorize_path %>
    <% end %>
    
  7. 新增 app/controllers/users/omniauth_callbacks_controller.rb 這個檔案,內容如同 https://github.com/ihower/rails-exercise-ac7/blob/master/app/controllers/users/omniauth_callbacks_controller.rb

  8. 編輯 app/models/user.rb,新增一個類別方法self.from_omniauth(auth),如同 https://github.com/ihower/rails-exercise-ac7/blob/master/app/models/user.rb#L40-L69

範例 Source Code:

  • https://github.com/ihower/rails-exercise-ac7
  • https://github.com/ihower/rails-exercise-ac8

常見問題

  • facebook 回傳的 hash 沒有包含 email ?
    • 用戶需要認證 facebook 上的 email 才會有 email,試試看重新在 Facebook 上編輯你的 email 資料。參考自 https://github.com/mkdynamic/omniauth-facebook/issues/61#issuecomment-124914162
  • facebook oauth 回來的網址結尾長得很奇怪 /#_=_
    • 這是正常現象,參考自 http://stackoverflow.com/questions/7485111/weird-url-appended

Authorization

在讓使用者登入之後,如果需要進一步設計使用者權限,可以這麼做:

Simple Role-based Authorization

新增 migration,在 users 上加一個欄位是 role

add_column :users, :role, :string

編輯 user.rb

def admin?
  self.role == "admin"
end

在 application_controller.rb 中加上:

protected

def authenticate_admin
  unless current_user.admin?
    flash[:alert] = "Not allow!"
    redirect_to root_path
  end
end

接著在需要保護的 Controller 加上

before_action :authenticate_user!  # 這個是 devise 提供的方法,先檢查必須登入
before_action :authenticate_admin # 再檢查是否有權限

這樣就是最基本的用 role 來檢查權限了。你可以在後台做使用者編輯的功能,可以選擇有哪些角色。

除了自行實作之外,也有一些函式庫可以幫助你設計,最知名的有兩個 gem:


Copyright © 2010-2021 Wen-Tien Chang All Rights Reserved.