rails注册与登录的实现步骤
注册
首先肯定是要有User资源的:
rails g scaffold Users name email password_digest
,其中password_digest
是为了对密码做hash保证不会明文存储。对于User资源,首先有个
show
视图来显示个人信息,在Users_controller#show
方法只需要根据用户请求的url
中的id
值在数据库中进行查找,然后将该@user
实例变量在show
视图中显示即可:def show;@user = User.find params[:id];end
完成注册表单,首先在
Users_controller#new
中新建一个@user
实例变量,然后在new
视图中用form_fo
r辅助方法对@user
进行赋值,默认情况是form_for
提交的方式是对/users
进行post
请求,也就是对应了Users_controller#create
动作,符合REST架构。<%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account" %> <% end %>
form_for
会将用户的输入封装到params[:user]
中,如果直接用这一个参数来createuser
的话可能会导致用户能自己提交许多不必要的参数,因此需要使用健壮参数,只允许某几个属性用来createuser
def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end
于是User_controller#create动作的整体方案就是用健壮参数来创建@user,如果@user保存成功,即各属性都通过validation,则转到show视图,如果失败则重新渲染new视图并且显示错误信息。
class UsersController < ApplicationController def create @user = User.new(user_params) if @user.save # 处理注册成功的情况 redirect_to @user else render 'new' end end end
对注册失败的情况进行处理:注册失败的原因一般是邮箱已存在,或者注册的字段validation没通过之类的。在注册失败之后需要重新渲染new视图,并且显示错误信息,错误信息可以存放在partial中,然后在new视图里面渲染,通过 @user.errors.full_messages来访问错误信息。
如果注册成功了,则将用户信息存库,并且重定向到show视图,并显示欢迎信息。显示欢迎信息可以用flash来实现,flash是一个hash表,键有很多,通常表示操作成功的是:success,表示操作成功,值就是显示的信息。
登录
登录和退出实际上就是session的创建和销毁的过程,所以首先要创建Session控制器:
rails g controller Sessions new
,这里只创建new动作是因为只有new动作需要视图,create和destroy都不需要。给
Sessions
的各个动作分配具名路由,如:get '/login', to: 'sessions#new'
,post
和delete
对应create
和destroy
动作。new
动作对应的视图是一个登录表单,登录表单和注册表单有很大的区别,注册表单使用form_for @user
来对/users
进行post
请求,但是登录表单因为session
并不是Activerecord
模型,所以要指定资源的名称和请求的url
:form_for(:session, url: login_path)
。<%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.submit "Log in" %> <% end %>
之后需要定义
Sessions#create
动作,与Users#create
类似,首先通过params
中的参数查询数据库中是否有记录,如果记录存在并且params
中的密码能authenticate
正确,则登陆成功,否则登录失败def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # 登入用户,然后重定向到用户的资料页面 else # 创建一个错误消息 render 'new' end end
当登录失败时,需要重新渲染
new
视图,并且显示错误信息,此处的错误信息和之前注册user
的不一样,因为User
是ActiveRecord
模型,所以错误信息会保存在@user.errors
方法中,Session并不是,所以此处错误信息必须通过flash
来传递,因为是登录失败,所以键是:danger
:flash.now[:danger] = 'Invalid email/password combination'
。一般来说flash
在一个请求过程是一直存在的,重新render
一个页面还是在原来的请求上下文中,所以flash
会一直显示,flash.now
则专门用于重新渲染的页面中显示信息。对于登入,可以再
Session_Helper
中定义一个log_in
方法,将接受的user
的id
属性存入session:
session[:user_id] = user.id`,
session方法创建的临时
cookie会自动加密 ;于是在
sessions_controller#create动作中如果用户存在且通过验证,则调用log_in
方法将该用户的id
存入session
。在
Session_Helper
中也可以定义一个current_user
方法,如果session
中已经保存了该用户的id
,则该用户为当前用户:@current_user ||= User.find_by(id: session[:user_id])
,如果@current_user
不为空表示用户已登录。为了保证用户注册之后要直接登录,所以在
users_controller#create
动作中,如果@user
保存成功,则调用log_in
方法保存其id
(为了在别的controller
中能使用session#helper
的方法,需要在application_controller
中include
模块)登出对应的
Sessions_controller#destroy
方法,主要操作就是将session
中的user_id
删除,并将@current_user
清空,之后重定向到root
页面。以上的实现基于的是
session
,当用户关闭浏览器后再次访问网站时,就不是登录状态了,并且现在主流应用都有记住用户登录状态的功能,所以需要使用持久性cookie
来处理。创建
cookie
的流程:- 生成随机字符串作为
remember_token
- 把这个
token
存入浏览器的cookie
中,并把过期时间设为未来的某个日期 - 在数据库中存储令牌的
remember_digest
- 在浏览器的
cookie
中存储加密后的用户ID
- 如果
cookie
中有用户的ID
,就用这个ID
在数据库中查找用户,并且检查cookie
中的remember_token
和数据库中的remember_digest
是否匹配
- 生成随机字符串作为
首先,
rails generate migration add_remember_digest_to_users remember_digest:string
,给Users
添加remember_digest
这一属性。然后使用Ruby 标准库中SecureRandom
模块的urlsafe_base64
方法来创建随机字符串。由于
remember_digest
是数据库中真实存在的列,而remember_token
其实只是虚拟属性,所以需要给User
类创建remember_token
属性:attr_accessor :remember_token
,类似于has_secure_password
,我们需要定义一个remember
方法,用于创建用户的remember_token
,然后更新数据库中的remember_digest
,将remember_token
加密后的内容赋给它:def remember self.remember_token = SecureRandom.urlsafe_base64 update_attribute(:remember_digest, User.digest(remember_token)) end
定义好
user.remember
方法之后,就能创建持久会话了,即把加密之后的用户id
和remember_token
存入cookie
,并且设置过期时间;之后类似于判断password
和password_digest
是否匹配的authenticate
方法,我们要定义判断remember_token
和remember_digest
是否匹配的authenticate?
方法:def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end
在
User_helper
中定义remember
方法,首先调用user.remember
来创建remember_token
和remember_digest
,然后将user
的id
和remember_token
都加密过后存入cookies
:def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end
然后是要修改原有的
current_user
方法,因为它只会从session
中取user_id
,应该改为如果session
中有user_id
的话就从session
中取,不然就从cookies
中取:def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end
对于忘记用户的话,只需要给
User
模型添加forget
方法,将数据库中的remember_digest
置空,然后再cookies
中删除user_id
和remember_token
,最后在log_out
方法中先将当前用户忘记登录即可。别忘了在sessions_controller#destroy
动作中在log_out
之前先要判断是否是登入状态。最后一步是在登录页面创建复选框,这一步其实很简单,首先在视图中添加复选框的代码,然后根据复选框四否被选中来决定是
remember
还是forget
:<%= f.label :remember_me, class: "checkbox inline" do %> <%= f.check_box :remember_me %> <span>Remember me on this computer</span> <% end %> #在sessions_controller#create动作中,log_in之后,重定向之前 params[:session][:remember_me] == '1' ? remember(user) : forget(user)
完成!