论坛首页 编程语言技术论坛

Rails每周一题(一): Restful Authentication

浏览 5178 次
精华帖 (0) :: 良好帖 (5) :: 新手帖 (0) :: 隐藏帖 (2)
作者 正文
   发表时间:2009-03-23   最后修改:2010-08-15

什么是Restful Authenticaiton:

 

Restful Authentication是Rails的认证系统插件,它为你生成一个REST风格的认证模板。

 

具体的概念和生成操作请见:Rails宝典之六十七式:restful_authentication   , Rails插件:Restful Authenticaiton.

 

本篇为你讲述Restful Authentication的具体实现。

 

认证系统:

 

想象一下,现在大部分网站所提供的认证系统是什么样子的?

 

流程:注册 -》 激活 -》登录 -》登出

 

注册需要什么?    用户名,密码,email。

 

记住我:登录时可以选择记住用户名密码一段时间,在这段时间内不再用输入用户名和密码。

 

这是现在大部分网站的注册和登录系统需要的模板。而只要一个命令,Restful Authentication就为你生成了User model, 管理注册和登录的controller,相应的页面,mailer等等。让我们来看看代码吧。

 

实现:

 

User model:

 

  # attributes相关的代码

  attr_accessor :password    # 不需要保存明文密码到数据库

  validates_presence_of     :login, :email
  validates_presence_of     :password,                   :if => :password_required?
  validates_presence_of     :password_confirmation,      :if => :password_required?
  validates_length_of       :password, :within => 4..40, :if => :password_required?
  validates_confirmation_of :password,                   :if => :password_required?
  validates_length_of       :login,    :within => 3..40
  validates_length_of       :email,    :within => 3..100
  validates_uniqueness_of   :login, :email, :case_sensitive => false
  before_save :encrypt_password  # 为密码加密

  # anything else you want your user to change should be added here.
  # mass assignment
  attr_accessible :login, :email, :password, :password_confirmation

  # Encrypts some data with the salt.
  def self.encrypt(password, salt)
    Digest::SHA1.hexdigest("--#{salt}--#{password}--")
  end

  protected
    
    def encrypt_password  # 加密密码
      return if password.blank?
      self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
      self.crypted_password = encrypt(password)
    end

     def password_required?  # 当注册时和修改密码时
       crypted_password.blank? || !password.blank?
    end

 

显而易见,这里为大家生成了一个基本的User model,它包含注册所需的一些基本信息,比如用户名,密码,email。并加上了一些简单的validates,以及为密码加密等。

 

# 与激活有关的代码

before_create :make_activation_code   # 创建时生成一个激活码

 # Activates the user in the database.
  def activate    # 激活,把activation_code置为nil
    @activated = true
    self.activated_at = Time.now.utc
    self.activation_code = nil
    save(false)
  end

  def active?
    # the existence of an activation code means they have not activated yet
    activation_code.nil?
  end

  # Returns true if the user has just been activated.
  def recently_activated?
    @activated
  end

 protected

   def make_activation_code
      self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
    end
 

 

  # 与认证有关的代码

  # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
  # 查找数据库是否有相关login的用户存在,并匹配密码。
  def self.authenticate(login, password)
    u = find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login] # need to get the salt
    u && u.authenticated?(password) ? u : nil
  end

   def authenticated?(password)
    crypted_password == encrypt(password)
  end
 

 

  # 与“记住我”相关的代码

  def remember_token?
    remember_token_expires_at && Time.now.utc < remember_token_expires_at 
  end

  # These create and unset the fields required for remembering users between browser closes
  def remember_me
    remember_me_for 2.weeks
  end

  def remember_me_for(time)
    remember_me_until time.from_now.utc
  end

  def remember_me_until(time)
    self.remember_token_expires_at = time
    self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
    save(false)
  end

  def forget_me
    self.remember_token_expires_at = nil
    self.remember_token            = nil
    save(false)
  end

 

SessionsController:

 

  # 当remember me时,设置cookie。
  cookies[:auth_token] = { :value => self.current_user.remember_token , 
                                              :expires => self.current_user.remember_token_expires_at }
 

就主要介绍一下model内的代码吧。controller, mailer, observer的代码都比较简单,不再赘述。不过有块代码比较重要,那就是lib下面的AuthenticatedSytem module。

 

这个module里面比较重要的方法有:

 

   # 设置session 
   # Store the given user id in the session.
    def current_user=(new_user)
      session[:user_id] = new_user ? new_user.id : nil
     # 如果认证失败,则赋@current_user为false,避免在获取current_user时再访问数据库
      @current_user = new_user || false
    end

 

 

    # Accesses the current user from the session. 
    # Future calls avoid the database because nil is not equal to false.
    # 此方法将被login_required调用,它会去尝试用三种不同的方式认证。其中第三种直接用客户端存储的
    # auth_token cookied进行认证(即remember me的方式)。
    def current_user
      @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie) unless @current_user == false
    end

    def login_required
      authorized? || access_denied
    end

    def logged_in?
      !!current_user
    end

    def authorized?
      logged_in?
    end

    # Called from #current_user.  First attempt to login by the user id stored in the session.
    def login_from_session
      self.current_user = User.find_by_id(session[:user_id]) if session[:user_id]
    end

    # Called from #current_user.  Now, attempt to login by basic authentication information.
    def login_from_basic_auth
      authenticate_with_http_basic do |username, password|
        self.current_user = User.authenticate(username, password)
      end
    end

    # Called from #current_user.  Finaly, attempt to login by an expiring token in the cookie.
    def login_from_cookie
      user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
      if user && user.remember_token?
        cookies[:auth_token] = { :value => user.remember_token, :expires => user.remember_token_expires_at }
        self.current_user = user
      end
    end
 

代码就讲到这里吧。其实代码挺好懂,可以直接看生成的代码。

 

配置

 

在生成完代码之后,来看看如何进行一些简单的配置。

 

首先是router和observer的配置,很多教程都有。不再赘述。主要讲一下如何给controller设置filter吧。

 

一般在application.rb里面加上这两句,这样所有的controller都需要登录才能访问。

 

 

  include AuthenticatedSystem

  before_filter :login_required

 

当然,你可以为一些controller skip_before_filter。比如UsersController和SessionsController中的一些action就应该skip。

 

关于这个include AuthenticatedSystem,有个很奇妙的地方。

 

我们发现里面的current_user方法不仅在controller里面可以被使用,而且在view里面也可以直接被使用。

 

原来是因为:

 

    # Inclusion hook to make #current_user and #logged_in?
    # available as ActionView helper methods.
    def self.included(base)
      base.send :helper_method, :current_user, :logged_in?
    end
 

 

Rails Session

 

看到这里,可能大家有个疑惑。在Rails里面是怎么直接拿到session的。通过观察,我发现Rails对session已经做了管理。你可以直接看cookie,会发现有一个session_id,它利用这个session_id直接找到你的session。所以,你的使用就如此简单:

 

session[:user_id]
 

完毕

 

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics