什么是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]
完毕
发表评论
文章信息
- 由andyhu1007在2009-07-13创建
- 由andyhu1007在2011-05-26更新
- 标签: rails, restful authentication
2 楼 andyhu1007 2009-07-14 00:11
1 楼 netfork 2009-07-14 00:06