homeASCIIcasts

209: Introducing Devise 

(view original Railscast)

Other translations: En Es It Ja

Written by 夜明猪 (hlee.javaeye.com)

在我们之前的文章中我们已经介绍了一些登录和验证授权的解决方案,现在我们来介绍另外一个。最近,在ruby社区Devise越来越广泛的被采用来解决维护权限和验证。Devise源于Warden,而warden是一个基于Rack的验证权限gem,不过,使用devise实际并不需要任何关于warden的知识。

如果你之前有一些其他类似的维护验证权限功能的gem的使用经验的话,你会发现Devise的和他们的不同之处在于,提供了从页面到model的实现。相比而言,例如Authlogic就只实现了丢与model层的实现,这时你就要自己去处理view层实现。而Devise是基于Rails 引擎开发的所以就可以同时提供controllers和view的实现。从功能角度来看,Devise提供了11个方面的在维护和验证权限过程的功能模块,这些模块都是可配置的。例如,其中Rememberable模块是使用cookie保存用户的登录信息,Recoverable是用来处理用户重置口令的。可定制的模块使用可以很容易的根据自己的业务需求配置对应的相应功能模块。

给项目添加权限验证系统

那么,我们就通过一个实例来看看Devise是在项目中是如何使用的。下面的截图是一个用Rails3.0写的简单的项目管理应用。 Devise将会添加一个User model和提供权限控制功能。

Our project management application.

Devise可以很好的支持Rails 3 和Rails 2.3 在低的版本就不能支持了。而且,针对不同的Rails版本,我们需要选择不同的devise版本。Rails 3需要安装 Devise 1.1.rc0, 而rails2.3需要1.0.6。

那么,我们的项目是Rails3的就要应该把devise 的gem版本信息注明到gemfile里,如下:

/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/environments/development.rb

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

这个配置的意思似乎把localhost做为接发邮件的主机,如果,项目上线,在生产环境就需要在production中把action_mailer的host配置成域名。

生成devise的User Model

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:

/app/models/user.rb

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都会有什么字段。值得一提的是,表的字段和我们刚才的配置也有关系就是如果我们没有配置对应的功能模块,也就应该删除相对应的字段,和对应的索引。 那么,修改完我们需要的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的路径来调用注册模块。

The signup for that devise generates.

那么如果我们通过这个界面,我们完成注册,就会默认已经登录。这时,我们也可以通过/users/sign_out来登出。当然,如果这时我们再次试图通过访问/users/sign_in来登录,并输入刚才注册的用户名和口令。我们就会发现下面的错误:

Signing in throws an error on Rails 3 beta 2.

这实际是一个Rails 3.0 beta 2的问题,如果,我们看到这个错误,可以通过修改 /config/initializers/cookie_verification_secret.rb中的secret key来修正。其中,secret key是用来验证登录cookies的。

/config/initalizers/cookie_verification_secret.rb

# 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的部分,如下:

/config/application.rb

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  

重启后,我们应该可以正常登录。

Successfully signed up.

实际上,到目标我们已经完成了所有的验证权限的功能。接下来,我们可以进行一些改进。通常我们都愿意在页面上方显示用户是否已经登录的状态。如果登录就显示用户的一点信息,如果没有登录就显示登录和注册。

这样的需要很容易实现,我们可以在application的layout中添加对应的输出信息,以便在每个页面都可以看到同样的登录信息。如下:

/app/views/layouts/application.html.erb

<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_pathnew_user_session_path 分别表示注册和登录的路径。

The user's details are now shown at the top of each page.

那么,当我们把代码写好,刷新页面就可以看到对应的信息了。

Links to sign in or sign up are now shown when the user isn't logged in.

正如我们上面操作的,通过使用devise,添加很少的代码就可以拥有注册,登录,退出的功能。当然,devise还有更多的相关功能也相当容易上手使用。比如,重新设置密码的功能模块,也是在User的model里加上confirmable就可以拥有对应的功能。

The reset password page.

最后,可能我们会想到一个问题。因为,所有的登录,权限相关的界面也都是devise生成的,那么,如果,我们希望页面风格和我们自己的网站的风格一致,怎么办?实际上,devise也考虑到了这一点,提供了很容易的定制devise的途径,我们将在下一篇中介绍。