【Ruby on Rails全栈课程】3.6 登录功能--session、cookie

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 1、功能描述(1)登录需要填写信息:邮箱、密码。当邮箱没有注册需要进行相应的提示。(2)将数据库保存的密码解密后,与用户在页面输入的密码作对比,相同可登录。(3)用户角色为管理员时,需要判断这个账号的状态,状态为未激活时(status为1),需要flash.notice提醒激活。2、编辑controller、view、路由(1)在路由文件config/routes.rb中添加路由,通过此链接来提交在登录页面提交的信息

1、功能描述


(1)登录需要填写信息:邮箱、密码。当邮箱没有注册需要进行相应的提示。


(2)将数据库保存的密码解密后,与用户在页面输入的密码作对比,相同可登录。


(3)用户角色为管理员时,需要判断这个账号的状态,状态为未激活时(status为1),需要flash.notice提醒激活。


2、编辑controller、view、路由


(1)在路由文件config/routes.rb中添加路由,通过此链接来提交在登录页面提交的信息


post 'create_login' => 'accounts#create_login'


(2)编辑views/accounts/login.html.erb文件,将form_tag后面的链接改成/create_login


<!--原代码-->
<%= form_tag "#" do %>
<!--改为-->
<%= form_tag "/create_login" do %>


3)在accounts_controller.rb中添加create_login方法


def create_login
end


3、获取页面提交的数据,实现登录功能


(1)rails s启动项目,在浏览器中打开注册页面http://localhost:3000/login(注意mac电脑是http://192.168.33.10:3000/login)随便填写信息,点击登录按钮,回到终端,能看到终端返回的日志中包含以下params数据。

Parameters: {"utf8"=>"✓", "email"=>"猫宁", "password"=>"[FILTERED]", "commit"=>"登录"}


因为我们在root文件中指定了post 'create_login' => 'accounts#create_login',所以数据提交到链接/create_login时,会用我们在accounts_controller中的create_login方法来获取并处理。


params是一个哈希,里面有4个key分别为utf8、email、password、commit,在create_login方法中取出email的值应该像下面这样:


email = params[:email]


在注册时取email的值是email = params[:account][:email],因为from_tag和form_for的区别。可以回头复习一下,我们在3.4章中讲过。



(2)回到sublime,编辑我们刚刚在accounts_controller.rb中添加的create_login方法,实现登录功能


def create_login
  #从params中取email、password的值
  #strip是去除字符串头部和尾部的空格的方法
  email = params[:email].strip
  password_html = params[:password].strip
  #通过email查找用户
  account = Account.find_by(email:email)
  #如果用户存在,进行下面判断
  if account
    #用户的身份为管理员,状态为激活,密码正确,可以登录成功
    if account.role == 1 && account.status == 0
      #数据库存储的密码解密与用户输入的密码作对比
      if Des.des_decode(account.password).to_s == password_html
        flash.notice = "登录成功!"
        redirect_to :root
      else
        flash.notice = "用户名密码错误!"
        render :login
      end
    #如果用户为管理员身份,状态为未激活,需要提示激活信息
    elsif account.role == 1 && account.status == 1
      flash.notice = "您的用户未激活,请超级管理员激活后再重新登录"
      render :login
    #其他类型的客户,密码正确,可以登录
    else
      if Des.des_decode(account.password).to_s == password_html
        flash.notice = "登录成功!"
        redirect_to :root
      else
        flash.notice = "用户名密码错误!"
        render :login
      end
    end
  #如果用户不存在,需要提示用户去注册
  else
    flash.notice = "用户不存在,请先注册"
    render :login
  end   
end


代码解析:


redirect_to :root

:root指的是在路由文件中root路由root 'home#index', 登录成功后,页面自动跳转到网站主页面


4、用session保存已登录的用户id,在没有关闭浏览器的情况下,一直保持登录状态


(1)编辑create_login方法,加入session,session是Rails中原本就有的方法,可以直接拿来用。


在flash.notice = "登录成功!"代码的上面添加一行代码。


!!!注意在create_login方法中有两处这样的代码,都要加上


session[:account_id] = account.id
#参考代码,无需粘贴
#flash.notice = "登录成功!"

(2)在application_controller.rb文件,添加check_login方法,该方法用来检查当前是否有用户登录。


这个方法几乎在每个页面都需要加载,所以我们写在application_controller.rb里面


(2)在application_controller.rb文件,添加check_login方法,该方法用来检查当前是否有用户登录。
这个方法几乎在每个页面都需要加载,所以我们写在application_controller.rb里面


代码解析:


@current_user ||= session[:account_id] && Account.find(session[:account_id])

代码意思为:如果@current_user不为空,直接返回@current_user。如果@current_user为空,则查看session中的account_id值是否存在,如果存在则查找session中的account_id对应的用户信息并返回。如果没有登录则返回空。


相当于下面的代码:


def current_user 
  if @current_user 
    @current_user 
  else 
    if session[:account_id] 
      @current_user = Account.find(session[:account_id]) 
    else 
      @current_user = nil 
    end 
    @current_user 
  end
end 


||=符号的含义:


a ||= “hello” 当a为nil时,a的值为"hello";当a不为nil,返回a本身的值。


a ||= 3


返回值为a = 3


b = 2


b ||= 3


b已经被赋值,返回值为2


(3)在home_controller.rb文件中添加下面一行代码


#参考代码,无需粘贴
#class HomeController < ApplicationController
before_action :check_login, only:[:index]


代码解析:


before_action :check_login, only:[:index]

代码意思为:在每次执行index方法之前,都要先执行application_controller.rb中的check_login方法,检查目前是否有用户登录。


before_action :check_login

去掉only限制,意思为:在home_controller中的每个action方法执行之前,都要先执行check_login方法


(4)在accouns_controller.rb中添加下面一行代码


#参考代码,无需粘贴
#class AccountsController < ApplicationController
before_action :check_login, except:[:signup,:create_account,:login,:create_login,:logout]


代码解析:


在accouns_controller.rb中所有的action方法中,除了signup、create_account、


login、create_login、logout方法。其他action执行前都要先执行application_controller.rb中的check_login方法,检查目前是否有用户登录。


(5)rails s启动项目,打开登录页面,登录上一节注册的账号,测试是否保存了session。我们测试主要是帮助大家更加深入了解一下session。


登录成功后,在主页面任意位置点击右键—检查(Windows系统按F12键)—Application—Cookies里面有一个session



image.png

说明session已经保存成功了。这是Chrome浏览器的检查方法。


5、主页面右上角显示


登录成功后,页面头部右测应该显示用户名称等信息,左侧根据用户角色的不同显示相应的信息,而不是登录注册链接


在views/layouts/application.html.erb文件中,修改下面信息:


<!--原代码-->
<ul class="nav right">
  <li><%= link_to "登录","/login" %></li>
  <li><%= link_to "注册","/signup" %></li>
</ul>
<!--改为-->
<ul class="nav left">
<!--当前有用户登录,并且该用户为管理员,显示关于、用户管理、帖子管理-->
  <% if @current_user && @current_user.role == 1 %>
    <li><%= link_to "关于","#" %></li>
    <li><%= link_to "用户管理","#" %></li>
    <li><%= link_to "帖子管理", "#"%></li>
<!--当前有用户登录,并且该用户为超级管理员,显示关于、管理员账户激活-->
  <% elsif @current_user && @current_user.role == 2 %>
    <li><%= link_to "关于","#" %></li>
    <li><%= link_to "管理员账户激活", "#" %></li>
<!--当前有用户登录,并且该用户为普通用户,显示关于-->
  <% elsif @current_user && @current_user.role == 0 %>
    <li><%= link_to "关于","#" %></li>
  <% end %>
</ul>
<ul class="nav right">
  <!--当前有用户登录,右侧显示该用户的相关信息-->
  <% if @current_user %>
    <li><%= link_to "用户名:"+@current_user.name,"#" %></li>
    <li><%= link_to "权限:"+Account::ROLE[@current_user.role],"#"%></li>
    <li><%= link_to "退出","#",method:'delete' %></li>
  <!--当前没有用户登录,右侧显示注册、登录链接-->
  <% else %>
    <li><%= link_to "登录","/login" %></li>
    <li><%= link_to "注册","/signup" %></li>
  <% end %>
</ul>


现在用不同角色的账号登录后,主页面会显示当前用户信息以及对应的功能信息了


image.png


6、退出登录,注销session


1)routes.rb中加上


delete 'logout' => "accounts#logout"


delete、post、get有什么区别呢?


get,post,put,delete是与服务器交互的4种方法,对应着查,改,增,删4个操作。

get一般用于获取数据的,post一般用于更新数据。对资源的增,删,改,查操作,其实都可以通过get、post完成,不需要用到put和delete。


其实这4种交互方法的使用没有强制要求,但是我们尽量按照规范来。


(2)accounts_controller.rb中加上相应action方法


点击退出链接,通过/logout链接执行logout方法,将session[:account_id]的值置为nil,并且重定向到主页面。


(2)accounts_controller.rb中加上相应action方法
点击退出链接,通过/logout链接执行logout方法,将session[:account_id]的值置为nil,并且重定向到主页面。


(3)修改views/layouts/application.html.erb,加上/logout链接


<!--原代码-->
<li><%= link_to "退出","#",method:'delete' %></li>
<!--改为-->
<li><%= link_to "退出","/logout",method:'delete' %></li>


7、将session换成cookie来持久化登录


(1)session和cookie的区别


session以文件的形式存储在web服务器上,适合存储临时数据。cookie以文件的形式存储到计算机本地,存储数据时间长


如果网站关闭或者关机重启了,session数据可能会丢失,但是cookie数据不会出现这种问题


cookie数据存储在本地,数据容易被伪造,安全性不高


(2)修改login.html.erb页面,添加「记住账号」勾选框,粘贴下面四行代码到文件中


<dl class="form remember-me">
  <%= check_box_tag :remember_me, 1, params[:remember_me] %>
  <%= label_tag "记住账号" %>
</dl>
<!--参考代码,无需粘贴-->
<!--<p><%= submit_tag "登录", :class => "login-button btn btn-primary" %></p>-->


(3)因为cookie数据存储在本地,如果直接存储account_id数据很容易被伪造,所以我们给用户生成属于这个用户唯一的16位随机数,来替代account_id代表这个用户。

A、需要再account表中添加一个新的字段来保存这个16位随机数,我们给这个字段取名为auth_token,在项目命令行下执行下面代码:


/vagrant/data_system$ rails g migration AddAuthTokenToAccounts auth_token:string
#系统返回信息,产生了一个映射文件
create    db/migrate/20180928104207_add_auth_token_to_accounts.rb
#将新创建的映射文件映射到数据库中
/vagrant/data_system$ rake db:migrate
#系统返回信息
== 20180928104207 AddAuthTokenToAccounts: migrating ===========================
-- add_column(:accounts, :auth_token, :string)
   -> 0.0259s
== 20180928104207 AddAuthTokenToAccounts: migrated (0.0272s) ==================


B、在app/models/account.rb文件中添加以下代码

添加的generate_token方法中,代码的意思为,先将auth_token字符置为nil,再运行auth_token = SecureRandom.urlsafe_base64方法生成16位随机数,再通过while

Account.exists?(auth_token:"#{auth_token}")条件来判断目前已保存的Account数据中是否存在刚刚生成的16位随机数据,如果存在则重新生成,直到不存在为止,虽然概率非常小,但我们需要保证auth_token的唯一性。最后将唯一的auth_token值保存到数据库中。



#参考代码,无需粘贴
#class Account < ApplicationRecord
before_create :generate_token
ROLE = {0 => "普通用户",1 => "管理员",2 => "超级管理员"}
def generate_token
  auth_token = nil
  begin
    auth_token = SecureRandom.urlsafe_base64
    Rails.logger.info "===auth_token==========#{auth_token}"
  end while Account.exists?(auth_token:"#{auth_token}")
  self.auth_token = auth_token
end
#参考代码,无需粘贴
#end


代码解析:


begin


代码..


end while 条件


是一种while循环格式,先执行代码,再看条件是否为true,为true就继续循环执行代码,为false就停止循环。至少会执行一次代码


before_create :generate_token


代码意思是,在account创建之前,先执行generate_token方法(刚刚在account.rb文件中创建的方法),也就是在account.save操作之前,会先调用generate_token方法,然后再进行保存操作


Rails.logger.info "===auth_token==========#{auth_token}"

一般在测试里面使用,将你想要展示的内容打印到日志里面,我们这行打印生成的auth_token,来确认auth_token成功生成了。


auth_token = SecureRandom.urlsafe_base64

Rails中生成16位随机数的一个方法,可以直接调用来生成16位随机数


C、处理老数据


我们之前创建的用户,auth_token字段都是空的,我们需要处理一下


#进入控制台
/vagrant/data_system$ rails c
#粘贴下列代码
Account.where(auth_token:nil).each do |a|
  auth_token = nil
  begin
    auth_token = SecureRandom.urlsafe_base64
  end while Account.exists?(auth_token:"#{auth_token}")
  a.auth_token = auth_token
  a.save
end


这样老数据也有auth_token字段啦


(4)将session有关的代码调整为cookies相关的代码


A、accounts_controller.rb中调整create_login方法,注意需要修改两处


#原代码
session[:account_id] = account.id
#改为
if params[:remember_me]
  cookies.permanent[:auth_token] = account.auth_token
else 
  cookies[:auth_token] = account.auth_token
end


代码解析:


cookies.permanent[:auth_token]

勾选remember_me,permanent方法会使cookie的到期时间是20年


cookies[:auth_token]

不勾选remember_me,cookie的到期时间是1小时


B、accounts_controller.rb文件中调整logout方法

#原代码:
session[:account_id] = nil
#改为
cookies.delete(:auth_token)


C、application_controller.rb文件中调整check_login方法


#原代码
@current_user ||= session[:account_id] && Account.find(session[:account_id])
#改为
@current_user ||= cookies[:auth_token] && Account.find_by(auth_token:cookies[:auth_token])


D、测试


勾选remember_me登录,右键检查—Application下面有一个auth_token的cookie,到期时间是2038年,说明成功了


image.png

目录
相关文章
|
1月前
|
存储 安全 搜索推荐
理解Session和Cookie:Java Web开发中的用户状态管理
理解Session和Cookie:Java Web开发中的用户状态管理
63 4
|
1月前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
2月前
|
缓存 Java Spring
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
文章比较了在Servlet和Spring Boot中获取Cookie、Session和Header的方法,并提供了相应的代码实例,展示了两种方式在实际应用中的异同。
194 3
servlet和SpringBoot两种方式分别获取Cookie和Session方式比较(带源码) —— 图文并茂 两种方式获取Header
|
2月前
|
存储 安全 数据安全/隐私保护
Cookie 和 Session 的区别及使用 Session 进行身份验证的方法
【10月更文挑战第12天】总之,Cookie 和 Session 各有特点,在不同的场景中发挥着不同的作用。使用 Session 进行身份验证是常见的做法,通过合理的设计和管理,可以确保用户身份的安全和可靠验证。
27 1
|
3月前
|
存储 缓存 数据处理
php学习笔记-php会话控制,cookie,session的使用,cookie自动登录和session 图书上传信息添加和修改例子-day07
本文介绍了PHP会话控制及Web常用的预定义变量,包括`$_REQUEST`、`$_SERVER`、`$_COOKIE`和`$_SESSION`的用法和示例。涵盖了cookie的创建、使用、删除以及session的工作原理和使用,并通过图书上传的例子演示了session在实际应用中的使用。
php学习笔记-php会话控制,cookie,session的使用,cookie自动登录和session 图书上传信息添加和修改例子-day07
|
2月前
|
存储 安全 搜索推荐
深入探索研究Ruby CGI Session
【10月更文挑战第3天】
30 0
|
3月前
|
存储 前端开发 Java
JavaWeb基础7——会话技术Cookie&Session
会话技术、Cookie的发送和获取、存活时间、Session钝化与活化、销毁、用户登录注册“记住我”和“验证码”案例
JavaWeb基础7——会话技术Cookie&Session
|
3月前
|
存储 安全 NoSQL
Cookie、Session、Token 解析
Cookie、Session、Token 解析
66 0
|
3月前
|
存储 安全 搜索推荐
探索研究Ruby CGI Session
【9月更文挑战第3天】
31 1
|
4月前
|
C# 开发者 Windows
WPF遇上Office:一场关于Word与Excel自动化操作的技术盛宴,从环境搭建到代码实战,看WPF如何玩转文档处理的那些事儿
【8月更文挑战第31天】Windows Presentation Foundation (WPF) 是 .NET Framework 的重要组件,以其强大的图形界面和灵活的数据绑定功能著称。本文通过具体示例代码,介绍如何在 WPF 应用中实现 Word 和 Excel 文档的自动化操作,包括文档的读取、编辑和保存等。首先创建 WPF 项目并设计用户界面,然后在 `MainWindow.xaml.cs` 中编写逻辑代码,利用 `Microsoft.Office.Interop` 命名空间实现 Office 文档的自动化处理。文章还提供了注意事项,帮助开发者避免常见问题。
295 0