使用者認證
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_name、last_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.erb和views/devise/registrations/new.html.erb,加上username欄位
<div><%= f.label :nickname %><br /> <%= f.text_field :nickname %></div>
Authentication: 使用 Omniauth
除了使用上述的Devise自行處理使用者帳號密碼之外,現在也非常流行直接使用外部的使用者認證系統,例如Google、Facebook、Yahoo、GitHub等等,一來絕大部分的使用者都已經有了這些大網站的帳號,不需要再註冊一次。二來你也不需要擔心儲存密碼的安全性問題。這些第三方服務都使用一種叫做 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 的步驟說明如下:
- 編輯
Gemfile
加上gem 'omniauth-facebook'
,然後執行bundle
-
新增 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
-
編輯
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"
-
編輯
app/models/user.rb
,加上:omniauthable, :omniauth_providers => [:facebook]
如下:devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :omniauth_providers => [:facebook]
-
編輯
config/routes.rb
devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }
-
編輯
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 %>
-
新增
app/controllers/users/omniauth_callbacks_controller.rb
這個檔案,內容如同 https://github.com/ihower/rails-exercise-ac7/blob/master/app/controllers/users/omniauth_callbacks_controller.rb - 編輯
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: