天天看點

流行權限管理 gem Devise簡介

[color=red]update(2010-8-31)[/color]

如果,在2.3版本的rails上運作,發現如下類似錯誤

[quote]$./script/generate devise:install

Couldn't find 'devise:install' generator

$./script/generate devise:views users

Couldn't find 'devise:install' generator

[/quote]

請參考devise在Rails 3和Rails 2的指令不一樣

$ruby script/generate devise_install
$ruby script/generate devise User
$ruby script/generate devise_views
           

在我們之前的文章中我們已經介紹了一些登入和驗證授權的解決方案,現在我們來介紹另外一個。最近,在ruby社群Devise越來越廣泛的被采用來解決維護權限和驗證。Devise源于Warden,而warden是一個基于Rack的驗證權限gem,不過,使用devise實際并不需要任何關于warden的知識。

如果你之前有一些其他類似的維護驗證權限功能的gem的使用經驗的話,你會發現Devise的和他們的不同之處在于,提供了從頁面到model的實作。相比而言,例如Authlogic就隻實作了丢與model層的實作,這時你就要自己去處理view層實作。而Devise是基于Rails 引擎開發的是以就可以同時提供controllers和view的實作。從功能角度來看,Devise提供了11個方面的在維護和驗證權限過程的功能子產品,這些子產品都是可配置的。例如,其中Rememberable子產品是使用cookie儲存使用者的登入資訊,Recoverable是用來處理使用者重置密碼的。可定制的子產品使用可以很容易的根據自己的業務需求配置對應的相應功能子產品。

[size=x-large]給項目添權重限驗證系統[/size]

那麼,我們就通過一個執行個體來看看Devise是在項目中是如何使用的。下面的截圖是一個用Rails3.0寫的簡單的項目管理應用。 Devise将會添加一個User model和提供權限控制功能。

[img]http://dl.iteye.com/upload/attachment/253473/de7f7ef2-b5a1-32d6-897d-4d3629020cdd.png[/img]

Devise可以很好的支援Rails 3 和Rails 2.3 在低的版本就不能支援了。而且,針對不同的Rails版本,我們需要選擇不同的devise版本。Rails 3需要安裝 Devise 1.1.rc0, 而rails2.3需要1.0.6。

那麼,我們的項目是Rails3的就要應該把devise 的gem版本資訊注明到gemfile裡,如下:

gem 'devise', '1.1.rc0' 
           

有了這個聲明,我們就可以通過bundle來安裝需要的gem和gem需要的依賴包了。

bundle install
           

接下來一步是通過generate安裝devise相關代碼。

$ rails generate devise_install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml

===============================================================================

Some setup you must do manually if you haven't yet:

  1. Setup default url options for your specific environment. Here is an
     example of development environment:

       config.action_mailer.default_url_options = { :host => 'localhost:3000' }

     This is a required Rails configuration. In production is must be the
     actual host of your application

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root :to => "home#index"

===============================================================================
           

運作上面的指令會産生一些檔案,包括config initializer下的初始化devise檔案,和config/local下的i18n顯示消息輸出的檔案。下面提示的是兩個需要手動執行的操作,一個是為應用配置email host。另外,一個是在route下配置root的路由。示範項目中預設的根路由已經配置了,就不用再配。但devise發郵件用email的主機還需要配置,也很簡單因為我們是開發環境,直接拷貝提示就行。

config.action_mailer.default_url_options = { :host => 'localhost:3000' } 
           

這個配置的意思似乎把localhost做為接發郵件的主機,如果,項目上線,在生産環境就需要在production中把action_mailer的host配置成域名。

[size=x-large]生成devise的User Model[/size]

Devise會使用User Model來控制和權限,而且,Devise提供了generator來生産User Model, 當然這個generato隻是節省時間,并不是必須運作的,手動生成User model完全可以。

$ rails generate devise User
      invoke  active_record
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
      inject  app/models/user.rb
      create  db/migrate/20100412200407_devise_create_users.rb
       route  devise_for :users
           

運作generator會生成一個model檔案,一個migration檔案和一個devise_for的路由。我們下面将一個一個來看看這些檔案的作用。

下面是generator生成的User Model:

class User < ActiveRecord::Base  
	  # Include default devise modules. Others available are:  
	  # :token_authenticatable, :lockable, :timeoutable and :activatable  
	  # :confirmable  
	  devise :database_authenticatable, :registerable,   
	         :recoverable, :rememberable, :trackable, :validatable  

	  # Setup accessible (or protected) attributes for your model  
	  attr_accessible :email, :password, :password_confirmation  
	end  
           

我們可以看到User model和普通的ActiveRecord的差別并不大,主要的差别是調用了devise方法,當然這也是配置的關鍵。Devise方法有很多的參數用來辨別是否使用對應的功能子產品。比如,我們在前文說過的:rememberable和:recoverable功能子產品。是以,正如我們看到的,devise就是用這樣簡單的方式來配置是否使用相對應的功能子產品。就是說,如果我們不想要确認password的功能,我們隻是需要把對應的confirmable從這裡删除就可以了。

同時,User類中還有attr_accessible方法,是用來描述可能用到的User表字段。也就是,如果我們需要在Model以為使用這個屬性,那麼,就要辨別清楚是否需要隻讀還是讀寫權限。

接下來,我們看看生産的migration檔案:

class DeviseCreateUsers < ActiveRecord::Migration  
	  def self.up  
	    create_table(:users) do |t|  
	      t.database_authenticatable :null => false  
	      # t.confirmable  
	      t.recoverable  
	      t.rememberable  
	      t.trackable  
	      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both  

	      t.timestamps  
	    end  

	    add_index :users, :email,                :unique => true  
	    # add_index :users, :confirmation_token,   :unique => true  
	    add_index :users, :reset_password_token, :unique => true  
	    # add_index :users, :unlock_token,         :unique => true  
	  end  

	  def self.down  
	    drop_table :users  
	  end  
	end  
           

資料庫的migration檔案就很容易了解了,和普通的migration檔案完全相同,辨別user都會有什麼字段。值得一提的是,表的字段和我們剛才的配置也有關系就是如果我們沒有配置對應的功能子產品,也就應該删除相對應的字段,和對應的索引。

Now that we’ve modified the migration to suit the modules we want to use we can run the database migration.

那麼,修改完我們需要的migration檔案就可以執行migration真正生成對應的資料庫結構。通過運作下面指令生成:

rake db:migrate
           

接下來,我們看看generator在router.rb檔案中的devise_for都産生了什麼路由.我們可以通過rake routes檢視:

new_user_session   GET    /users/sign_in                 {:controller=>"devise/sessions", :action=>"new"}
          user_session POST   /users/sign_in                 {:controller=>"devise/sessions", :action=>"create"}
  destroy_user_session GET    /users/sign_out                {:controller=>"devise/sessions", :action=>"destroy"}
                       POST   /users/password(.:format)      {:controller=>"devise/passwords", :action=>"create"}
         user_password PUT    /users/password(.:format)      {:controller=>"devise/passwords", :action=>"update"}
     new_user_password GET    /users/password/new(.:format)  {:controller=>"devise/passwords", :action=>"new"}
    edit_user_password GET    /users/password/edit(.:format) {:controller=>"devise/passwords", :action=>"edit"}
                       POST   /users(.:format)               {:controller=>"devise/registrations", :action=>"create"}
                       PUT    /users(.:format)               {:controller=>"devise/registrations", :action=>"update"}
     user_registration DELETE /users(.:format)               {:controller=>"devise/registrations", :action=>"destroy"}
 new_user_registration GET    /users/sign_up(.:format)       {:controller=>"devise/registrations", :action=>"new"}
edit_user_registration GET    /users/edit(.:format)          {:controller=>"devise/registrations", :action=>"edit"}
           

稍微有點亂,當然,我們還是可以看出來,産生了如下路由:登入,登出,重置密碼,注冊,和修改。如果我們需要,所有這些路由都是可以配置的。

那麼,有了這些路由我們就可以通過制定的描述,來通路對應的功能子產品了。比如,我們應該通過通路/users/sign_up的路徑來調用注冊子產品。

[img]http://dl.iteye.com/upload/attachment/253475/57eaa8af-4f50-3614-9acf-b5dba67e5667.png[/img]

那麼如果我們通過這個界面,我們完成注冊,就會預設已經登入。這時,我們也可以通過/users/sign_out來登出。當然,如果這時我們再次試圖通過通路/users/sign_in來登入,并輸入剛才注冊的使用者名和密碼。我們就會發現下面的錯誤:

[img]http://dl.iteye.com/upload/attachment/253477/eef920de-48d3-30c1-abb1-ce820fb2ae17.png[/img]

這實際是一個Rails 3.0 beta 2的問題,如果,我們看到這個錯誤,可以通過修改 /config/initializers/cookie_verification_secret.rb中的secret key來修正。其中,secret key是用來驗證登入cookies的。

# Be sure to restart your server when you modify this file.  

	# Your secret key for verifying the integrity of signed cookies.  
	# If you change this key, all old signed cookies will become invalid!  
	# Make sure the secret is at least 30 characters and all random,   
	# no regular words or you'll be exposed to dictionary attacks.  
	Rails.application.config.cookie_secret = '3b161f7668f938d1aeb73e1137964f8d5ebaf32b9173c2130ecb73b95b610702b77370640dce7e76700fb228f35f7865ab2a5ccd22d00563504a2ea9c3d8dffe'
           

我們需要做的就是在這個檔案中删除相關描述,并且/config/application.rb檔案中删除Rails.application的部分,如下:

require File.expand_path('../boot', __FILE__)  
	require 'rails/all'  

	Bundler.require(:default, Rails.env) if defined?(Bundler)  

	module ProjectManage  
	  class Application < Rails::Application  
	    config.filter_parameters << :password  
	    config.cookie_secret = '3b161f7668f938d1aeb73e1137964f8d5ebaf32b9173c2130ecb73b95b610702b77370640dce7e76700fb228f35f7865ab2a5ccd22d00563504a2ea9c3d8dffe'  
	  end  
	end  
           

重新開機後,我們應該可以正常登入。

[img]http://dl.iteye.com/upload/attachment/253479/4b04be0a-3b69-3fe8-9179-ae18a455983b.png[/img]

實際上,到目标我們已經完成了所有的驗證權限的功能。接下來,我們可以進行一些改進。通常我們都願意在頁面上方顯示使用者是否已經登入的狀态。如果登入就顯示使用者的一點資訊,如果沒有登入就顯示登入和注冊。

這樣的需要很容易實作,我們可以在application的layout中添加對應的輸出資訊,以便在每個頁面都可以看到同樣的登入資訊。如下:

<div id="user_nav">  
	  <% if user_signed_in? %>  
	    Signed in as <%= current_user.email %>. Not you?  
	    <%= link_to "Sign out", destroy_user_session_path %>  
	  <% else %>  
	    <%= link_to "Sign up", new_user_registration_path %> or  
	    <%= link_to "Sign in", new_user_session_path %>  
	  <% end %>  
	</div> 
           

簡單解釋一下,這段顯示登入資訊的代碼。這段代碼主要是通過devise提供的方法實作,其中,判斷是否登入是通過 user_signed_in? 如果登入了,就通過current_user來顯示目前使用者的email,而退出的路徑是devise通過 destroy_user_session辨別到/users/sign_out。同樣,如果沒有登入的話,可以通過 new_user_registration_path 和 new_user_session_path 分别表示注冊和登入的路徑。那麼,當我們把代碼寫好,重新整理頁面就可以看到對應的資訊了。

[img]http://dl.iteye.com/upload/attachment/253481/7c3d484c-bf05-3666-8c1e-872c5fa77201.png[/img]

如果我們點選sign out的連結,我們應該看到注冊和登入連結。

[img]http://dl.iteye.com/upload/attachment/253483/efea312d-0195-35c4-a69b-f4e7d0e114c9.png[/img]

正如我們上面操作的,通過使用devise,添加很少的代碼就可以擁有注冊,登入,退出的功能。當然,devise還有更多的相關功能也相當容易上手使用。比如,重新設定密碼的功能子產品,也是在User的model裡加上confirmable就可以擁有對應的功能。

[img]http://dl.iteye.com/upload/attachment/253485/c228de93-b98f-3eb7-a480-95d843b813e4.png[/img]

最後,可能我們會想到一個問題。因為,所有的登入,權限相關的界面也都是devise生成的,那麼,如果,我們希望頁面風格和我們自己的網站的風格一緻,怎麼辦?實際上,devise也考慮到了這一點,提供了很容易的定制devise的途徑,我們将在下一篇中介紹。

繼續閱讀