【Rails学习笔记】登录和退出功能实现

登录相对于前面几章来说是个相对复杂的流程,主要体现在必须记住用户,必须利用加密算法来保证安全性


1.分析下登录流程和需要的工作:

首先表单必须写好,界面部分需要处理用户为登录状态和登出状态两种情况

用户登录成功时,如何让系统记住用户,如何保证安全性

用户退出时,后台如何处理。

另外需要设置好路由规则


如何实现Session:

网络中常见的 session 处理方式有好几种:可以在用户关闭浏览器后清除 session;也可以提供一个“记住我”单选框让用户选择永远保存,直到用户退出后 session 才会失效。 我们选择使用第二种处理方式,即用户登录后,会永久的记住登录状态,直到用户点击“退出”链接之后才清除 session。


很显然,我们可以把 session 视作一个符合 REST 架构的资源,在登录页面中准备一个新的 session,登录后创建这个 session,退出则会销毁 session。不过 session 和 Users 资源有所不同,Users 资源使用数据库(通过 User 模型)持久的存储数据,而 Sessions 资源是利用 cookie 来存储数据的。cookie 是存储在浏览器中的简单文本。实现登录功能基本上就是在实现基于 cookie 的验证机制。


如何记住用户:

因为 HTTP 是无状态的协议,所以如果应用程序需要实现登录功能的话,就要找到一种方法记住用户的状态。维持用户登录状态的方法之一,是使用常规的 Rails session(通过 session 函数),把用户的 id 保存在“记忆权标(remember token)”中:

session[:remember_token] = user.id


session 对象把用户 id 保存在浏览器的 cookie 中,这样在网站的所有页面就都可以使用了。浏览器关闭后,cookie 也随之失效。在网站中的任何页面,只需调用 User.find(session[:remember_token]) 就可以取回用户对象了。Rails 在处理 session 时,会确保安全性。倘若用户企图伪造用户 id,Rails 可以通过每个 session 的 session id 检测到。


根据示例程序的设计目标,我们计划要实现的是持久保存的 session,即使浏览器关闭了,登录状态依旧存在,所以,登入的用户要有一个持久保存的标识符才行。为此,我们要为每个用户生成一个唯一而安全的记忆权标,长期存储,不会随着浏览器的关闭而消失。


如何存储权标?

我们计划在浏览器中存储base64 权标,在数据库中存储加密后的版本。如果要自动登入用户,就可以从 cookie 中取出记忆权标,加密后查询数据库。数据库之所以只保存加密后的权标是因为,即便整个数据库都泄露了,攻击者也无法使用记忆权标登入网站。为了让记忆权标更安全,我们计划每次会话都生成不一样的权标,这样即使会话被劫持了(攻击者偷取 cookie 伪装成某个用户登录),用户下次登录时前一个会话就会失效。


用户注册成功后会自动登录吗:


真实的应用程序都会自动登入刚注册的用户(这样做的一个副作用就是创建了一个新的记忆权标),但是我们不想这么做,我们要用一种更好的方式,确保从一开始用户就有可用的记忆权标。



2.将登录和退出的测试文件authentication_pages_spec列出:

require 'spec_helper'

describe "AuthenticationPages" do
  subject { page }

  describe "signin" do
  	before {visit signin_path}

 
  	describe "with invalid information" do
      before { click_button "Sign in" }

      it { should have_title('Sign in') }
      it { should have_selector('div.alert.alert-error', text: 'Invalid') }

      describe "after visiting another page" do
        before { click_link "Home" }
        it { should_not have_selector('div.alert.alert-error') }
      end
    end

  	describe "with valid information" do
  		let(:user) {FactoryGirl.create(:user)}
  		before do
  			fill_in "Email", 	with: user.email.upcase
  			fill_in "Password", with: user.password
  			click_button "Sign in"
  		end

  		it { should have_title(user.name)}
  		it { should have_link('Profile', href: user_path(user)) }
  		it { should have_link('Sign out', href: signout_path)}
  		it { should_not have_link('Sign in', href: signin_path)}

      describe "followed by signout" do
        before {click_link "Sign out"}
        it { should have_link('Sign in')}
      end
  	end
  end
end

3.用户登录的表单如下:

<%= form_for(:session, url: sessions_path) do |f| %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.submit "Sign in", class: "btn btn-large btn-primary" %> <% end %>

New user? <%= link_to "Sign up now!", signup_path %>


4.用户认证需要的方法Sessions_Helps 如下:


module SessionsHelper
	def sign_in(user)
		remember_token = User.new_remember_token
		cookies.permanent[:remember_token] = remember_token
		user.update_attribute(:remember_token, User.encrypt(remember_token))
		self.current_user = user		
	end

	def sign_out 
		self.current_user = nil
		cookies.delete(:remember_token)
	end

	def signed_in?
		!current_user.nil?
	end

	def current_user=(user)
		@current_user = user
	end

	def current_user
		remember_token  =User.encrypt(cookies[:remember_token])
		@current_user ||= User.find_by(remember_token: remember_token)

	end
end

对于signin方法有以下说明:

上述代码组找了设定的步骤:首先,创建新权标;随后,把未加密的权标存入浏览器的 cookie;然后,把加密后的权标存入数据库;最后,把制定的用户设为当前登入的用户。

注意,保存记忆权标使用的是 update_attribute 方法,这样可以跳过数据验证更新单个属性。我们必须用这个方法,因为我们无法提供用户的密码及密码确认。


因为数据库中保存的记忆权标是加密的,所以在用来查找用户之前要加密从 cookie 中读取的权标


注意最后一个方法,我们获取当前用户用的是remember_token,但是cookies中存储的是为加密的权标,所以必须加密一次。

对于代码 @current_user ||= User.find_by(remember_token: remember_token)

只有第一次调用时才会去执行后面的部分,这里利用了||运算的短路性质


5.处理登录流程的代码如下:

class SessionsController < ApplicationController
	def new

	end

	def create
		user = User.find_by(email: params[:session][:email].downcase)
		if user && user.authenticate(params[:session][:password])
			sign_in user
      		redirect_to user
		else
			flash.now[:error] = 'Invalid email/password combination' # Not quite right!
      		render 'new'
		end
	end

	def destroy
		sign_out
		redirect_to root_path
	end

end

6.路由规则如下:

resources :sessions, only: [:new,:create,:destroy]

  #get "users/new"
  root 'static_pages#home'
  match '/signup', to: 'users#new', via:'get'
  match '/signin', to: 'sessions#new', via: 'get'
  match '/signout',to: 'sessions#destroy', via: 'delete'


注意signout对应的Http方法为delete


7.一些加密/产生记忆权标的方法将其放在Model中:

class User < ActiveRecord::Base
	before_save { self.email = email.downcase }
	before_create :create_remember_token

	VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
	validates :name, presence: true, length: { maximum: 50}
	validates :email, presence:true, 
		format: {with: VALID_EMAIL_REGEX},
		uniqueness: {case_sensitive: false}
	
	has_secure_password
	validates :password, length: {minimum: 6}

	def User.new_remember_token
		SecureRandom.urlsafe_base64
	end

	def User.encrypt(token)
		Digest::SHA1.hexdigest(token.to_s)
	end

	private
		def create_remember_token
			self.remember_token = User.encrypt(User.new_remember_token)
		end
end






你可能感兴趣的:(Ruby,on,Rails,Tutorial,Ruby,on,Rails)